Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
9d8a5ba
Update translations for dashboard
Jun 5, 2025
254dc17
update gitignore
doomlab Jun 5, 2025
cffc3fa
updating the widgets and sidedashbar
doomlab Jun 5, 2025
3e268db
Merge dev into translations
doomlab Sep 12, 2025
d26a9b8
fix merge conflict with widget
doomlab Sep 12, 2025
4b3b67e
adding information about what to do with emails for installation
doomlab Sep 17, 2025
6959410
Update README.md
doomlab Sep 17, 2025
1590712
update so people get added to tasks
doomlab Oct 10, 2025
88ae5d4
add start to breadcrumbs
doomlab Oct 10, 2025
bb4eed9
add rest of the breadcrumb labels
doomlab Oct 12, 2025
50015eb
Merge pull request #461 from STAPLE-verse/translations
doomlab Oct 12, 2025
a231e6a
fixing the scrolling with collapse cards
doomlab Oct 12, 2025
9c5151b
fixing odd spacing on project summary
doomlab Oct 13, 2025
14d3b13
Merge pull request #462 from STAPLE-verse/small-visual-updates
doomlab Oct 13, 2025
6d76c7a
make button match the rest visually
doomlab Oct 13, 2025
3902853
add copy task button
doomlab Oct 13, 2025
1357d65
Merge pull request #463 from STAPLE-verse/copy-task-button
doomlab Oct 13, 2025
a5752a0
starting email update
doomlab Oct 13, 2025
bea65b6
update viewer download names
doomlab Oct 14, 2025
6b136c0
update text for clarity on project summary
doomlab Oct 14, 2025
1c16642
adding teams to auto team tasks, sending notifications
doomlab Oct 14, 2025
8500b0d
adding new members to auto tasks and sending notifications
doomlab Oct 14, 2025
f1d540f
update task form to correctly save auto assign information
doomlab Oct 16, 2025
cd3b2c4
auto assign people
doomlab Oct 16, 2025
0fb367f
fix typescript errors
doomlab Oct 16, 2025
2eb4e4b
Merge pull request #464 from STAPLE-verse/add-all-feature
doomlab Oct 16, 2025
72d6a68
add link on notification to take user to notifications
doomlab Oct 17, 2025
706796c
Merge pull request #465 from STAPLE-verse/fix-project-summary
doomlab Oct 17, 2025
048f2dc
Merge pull request #466 from STAPLE-verse/update-email-notifications
doomlab Oct 17, 2025
87d2c3f
you can invite multiple people now
doomlab Oct 19, 2025
05bf233
add the s for clarity it's multiple people
doomlab Oct 19, 2025
d40c510
give them tags not tasks
doomlab Oct 19, 2025
8e17487
add information about whitelisting emails
doomlab Oct 19, 2025
ffb46e3
adding overdue tasks to daily email + clean up
doomlab Oct 19, 2025
75936a0
Merge pull request #467 from STAPLE-verse/invite-multiple-contributors
doomlab Oct 19, 2025
dee982c
Merge pull request #468 from STAPLE-verse/whitelist-email
doomlab Oct 19, 2025
70a0720
Merge pull request #469 from STAPLE-verse/overdue-task-reminder
doomlab Oct 19, 2025
f3a60df
add buttons to jump around
doomlab Oct 20, 2025
2b7efa8
update chatbox for markdown
doomlab Oct 20, 2025
40021d9
select all buttons across the app
doomlab Oct 20, 2025
1d5ebb7
add icon so you know what you are doing
doomlab Oct 20, 2025
4058d94
create anonymous task button and wire it up
doomlab Oct 20, 2025
3dfa013
avoid pulling data we don't want to show people
doomlab Oct 20, 2025
0160fd9
add back in ids we need for summary
doomlab Oct 20, 2025
05fe0f2
add aproved to the main page
doomlab Oct 20, 2025
03854ab
added to the individual task view page
doomlab Oct 20, 2025
1989f98
add approval to tables, change to icons to save space
doomlab Oct 20, 2025
4478bf5
fix typescript errors
doomlab Oct 20, 2025
5bde357
Merge pull request #470 from STAPLE-verse/add-role-and-form-jumper-bu…
doomlab Oct 21, 2025
9276238
Merge pull request #471 from STAPLE-verse/comments-textarea
doomlab Oct 21, 2025
8f3e319
Merge pull request #472 from STAPLE-verse/select-all-options
doomlab Oct 21, 2025
f454541
Merge pull request #473 from STAPLE-verse/mark-PM
doomlab Oct 21, 2025
a464e55
Merge pull request #474 from STAPLE-verse/anonymize-tasks
doomlab Oct 21, 2025
b20d5ed
Merge pull request #475 from STAPLE-verse/approval-markers
doomlab Oct 21, 2025
65a0643
sort forms newest to oldest and add date
doomlab Oct 21, 2025
33f6fc6
clean up task form to include date and name
doomlab Oct 21, 2025
e122936
clean up the task here so you know what to do
doomlab Oct 21, 2025
641de89
Update CONTRIBUTING.md
doomlab Oct 21, 2025
042a406
lower case emails
doomlab Oct 21, 2025
7836f97
Merge pull request #476 from STAPLE-verse/deal-with-form-select-view
doomlab Oct 21, 2025
e49b47e
make api pathways clear to avoid "duplicate" error
doomlab Oct 21, 2025
ae46fde
make sure no logs for stuff people shouldn't se
doomlab Oct 21, 2025
4bc61f0
hiding console logs
doomlab Oct 21, 2025
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
37 changes: 37 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,40 @@ flowchart LR
C -->|Declined| F[Resubmit if requested]
E -->|Accept| D[Merged dev]
```

## Developer Guidelines

### Clear and Helpful Commit Messages

When you make a commit (a save point in our project), it's important to describe what you've changed in a way that's easy to understand. Use simple, active sentences. For example, say "Update button styles" instead of something vague like "changed styling." This helps everyone see what's been done just by looking at the commit history.
Keep Your Branch Up-to-Date

If you’re working on a feature in a separate branch, it’s a good idea to regularly pull in the latest changes from the main branch. This helps prevent conflicts later and keeps your work relevant. Also, if you’ve been working on a branch for a long time without pulling updates, let the team know why in your pull request. This way, everyone understands your reasoning.

### Protecting the Main Branch

We want to make sure only high-quality code makes it into our main branch, which is what goes into production. To do this, we’ll require all changes to go through a pull request (PR). This means your code needs to be reviewed by someone else, and automated checks will be run to catch any issues before they become bigger problems.

### Keep Pull Requests Small and Focused

When possible, try to keep your pull requests small and focused on one thing. This makes it easier for others to review your changes quickly and reduces the chance of missing issues. If a pull request is too big, it can take longer to review and might introduce more bugs.

### Automated Testing

Before you commit your code, it’s important to write tests to make sure everything works as expected. Automated tests help keep our project stable by catching issues early. You can use GitHub Actions to run these tests automatically, so you know everything is working before you merge your changes into the main branch.

### Write and Keep Up Documentation

Good documentation is key to helping everyone understand how our project works. A clear README file and other documents can save a lot of time. It’s helpful to document both public info (like how to set up the project on your computer) and private info (like how our servers are managed). This way, everyone can find the information they need.

### Use GitHub Issues for Proposing Changes

Before you start working on something new, it’s a good idea to open a GitHub issue to get feedback. This can help you refine your ideas and make sure you’re on the right track. It also helps avoid doing work that might need to be redone later. Getting early feedback can save time and make sure your efforts are aligned with the team.

### Write Clear GitHub Issues

When you create a GitHub issue, try to include all the information needed to understand the problem or task. If you don’t have all the details, ask questions and tag the right people. A well-written issue is half the work, making it easier for someone to jump in and help.

### Communicate Clearly

Good communication is key to working well together. Before you send a message or comment, think about how others will read it. Try to make your point clear and simple. If you’re in a hurry, it’s easy to be misunderstood, so take a moment to check if your message is clear and concise. This will help others understand you better and keep things moving smoothly.
6 changes: 3 additions & 3 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ Copyright (c) 2025 STAPLE Development Team

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, subject to the following conditions:

1. **Attribution** must be given to the original authors of the STAPLE software.
2. **Non-Commercial Use Only**: The Software may not be used, in whole or in part, for commercial purposes without prior written permission from the copyright holders.
2. **Non-Commercial Use Only**: The Software may not be used, in whole or in part, for commercial purposes without prior written permission from the copyright holders.
- “Commercial purposes” include selling the Software, offering it as a service for a fee, or incorporating it into a commercial product or platform.
3. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

Expand Down
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
## STAPLE: Software for Scientists

Cite: [![DOI](https://zenodo.org/badge/665542257.svg)](https://doi.org/10.5281/zenodo.13916969)
*A research project management platform for open, transparent, and collaborative science.*

(use https://doi.org/10.5281/zenodo.13916969 for the concept version, or use the specific doi for a release if desired).
[![DOI](https://zenodo.org/badge/665542257.svg)](https://doi.org/10.5281/zenodo.13916969)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Build Status](https://github.com/STAPLE-verse/STAPLE/actions/workflows/dry-run.yml/badge.svg)](https://github.com/STAPLE-verse/STAPLE/actions/workflows/dry-run.yml)
[![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](https://github.com/STAPLE-verse/STAPLE?tab=contributing-ov-file)
[![Slack](https://img.shields.io/badge/chat-on%20Slack-purple.svg?logo=slack)]([https://your-slack-invite-link](https://join.slack.com/t/staple-talk/shared_invite/zt-25c08jrdt-f66do2kbIZExpAou5ZQYew))

Docs:
### 📖 Documentation:

Contribute:
👉 [Full installation and usage guide](https://staple.science/documentation/)

### 📦 Citation

If you use STAPLE in your work, please cite it using the concept DOI or a release-specific DOI:

- Concept DOI: https://doi.org/10.5281/zenodo.13916969
- For specific releases, use the DOI associated with that release (badge above links to the latest).

### 🤝 Contributing

We welcome contributions of all kinds — code, documentation, testing, and feedback.
[See our contribution guidelines.](https://github.com/STAPLE-verse/STAPLE?tab=contributing-ov-file)
162 changes: 143 additions & 19 deletions cron/cronJobMailer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import { resolver } from "@blitzjs/rpc"

const db = new PrismaClient() // Create Prisma client instance

function fmtDate(date) {
return moment(date).format("MMM D, YYYY")
}

// Helper function to create email content
function createDailyNotification(email, notificationContent) {
function createDailyNotification(email, notificationContent, overdueContent) {
const html_message = `
<html>
<body>
Expand All @@ -20,11 +24,15 @@ function createDailyNotification(email, notificationContent) {
<h3>STAPLE Daily Notifications</h3>

<p>
This email is to notify you about recent updates to your project.
Here are new announcements, tasks, and other project updates:
</p>
This email is to notify you about overdue tasks and recent updates to your project(s).
You can view all notifications on the <a href="https://app.staple.science/auth/login?next=%2Fnotifications">Notifications page</a>.
</p>

<h3>⏰ Overdue Tasks</h3>
<div style="margin:0 0 16px;">${overdueContent}</div>

${notificationContent}
<h3>📢 Project Updates</h3>
<div style="margin:0 0 16px;">${notificationContent}</div>
</body>
</html>
`
Expand Down Expand Up @@ -79,6 +87,85 @@ export async function fetchAndGroupNotifications() {
}, {})
}

// Function to fetch and group overdue tasks by email and project
export async function fetchAndGroupOverdueTasks() {
const now = new Date()

const tasks = await db.task.findMany({
where: {
deadline: { lt: now },
},
include: {
project: { select: { name: true } },
assignedMembers: {
include: {
users: { select: { email: true } },
},
},
taskLogs: {
select: {
id: true,
createdAt: true,
assignedToId: true,
status: true,
completedById: true,
completedAs: true,
},
orderBy: { createdAt: "desc" },
},
},
orderBy: { deadline: "asc" },
})

// A task counts as overdue for a member only if that member has a latest log and it is NOT_COMPLETED.
// If there is no log for that member, assume not assigned → do not include.
const isUnfinishedLatest = (log) => {
if (!log) return false
const s = (log.status || "").toString().toUpperCase()
return s === "NOT_COMPLETED"
}

// Group as: email -> projectName -> [task rows]
return tasks.reduce((acc, task) => {
const projectName = task?.project?.name || "No Project"
const taskName = task?.name || `Task #${task?.id}`
const due = task?.deadline ? fmtDate(task.deadline) : "no due date"
const pastDeadline = task?.deadline && task.deadline < now

// Map latest TaskLog by assignee (assignedToId) — schema note: TaskLog does not have projectmemberId
const latestByMember = new Map()
for (const log of task.taskLogs || []) {
if (!latestByMember.has(log.assignedToId)) {
latestByMember.set(log.assignedToId, log)
}
}

const members = task?.assignedMembers || []
if (members.length === 0) return acc

for (const m of members) {
const latest = latestByMember.get(m.id)
const isUnfinished = isUnfinishedLatest(latest)

if (pastDeadline && isUnfinished) {
const line = `${projectName} - ${taskName} - Due: ${due}`
const users = m?.users || []
for (const u of users) {
const email = u?.email
if (!email) continue
if (!acc[email]) acc[email] = {}
if (!acc[email][projectName]) acc[email][projectName] = []
acc[email][projectName].push(line)
}

// TODO: If assignment is to a team, add logic here to notify team distribution list or members
}
}

return acc
}, {})
}

// Function to introduce a delay (in milliseconds)
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

Expand All @@ -105,19 +192,43 @@ const checkRateLimit = async () => {
}
}
// Function to send grouped notifications
export async function sendGroupedNotifications(groupedNotifications) {
export async function sendGroupedNotifications(groupedNotifications, groupedOverdues) {
const delayTime = 500 // Delay time between each email in milliseconds (e.g., 1 second)

for (const [email, projects] of Object.entries(groupedNotifications)) {
const notificationContent = Object.entries(projects)
.map(([projectName, messages]) => {
const projectHeader = `<h4>Project: ${projectName}</h4>`
const messagesList = messages.map((message) => `<li>${message}</li>`).join("")
return projectHeader + `<ul>${messagesList}</ul>`
})
.join("")
const allEmails = new Set([
...Object.keys(groupedNotifications || {}),
...Object.keys(groupedOverdues || {}),
])

for (const email of allEmails) {
const projects = groupedNotifications?.[email] || {}

const emailContent = createDailyNotification(email, notificationContent)
const notificationContent =
Object.entries(projects)
.map(([projectName, messages]) => {
const projectHeader = `<h4>Project: ${projectName}</h4>`
const messagesList = messages.map((message) => `<li>${message}</li>`).join("")
return projectHeader + `<ul>${messagesList}</ul>`
})
.join("") || "<p>No new updates in the last 24 hours.</p>"

// Build overdue content for this recipient (if any)
const overdueProjects = groupedOverdues?.[email] || {}
const overdueContent =
Object.entries(overdueProjects)
.map(([projectName, rows]) => {
const projectHeader = `<h4>Project: ${projectName}</h4>`
const items = rows.map((row) => `<li>${row}</li>`).join("")
return projectHeader + `<ul>${items}</ul>`
})
.join("") || "<p>No overdue tasks 🎉</p>"

const emailContent = createDailyNotification(email, notificationContent, overdueContent)

console.log(
`[Mailer] Prepared email for ${email}: hasOverdues=${!!Object.keys(overdueProjects)
.length}, hasUpdates=${!!Object.keys(projects).length}`
)

// Check rate limit before sending email
await checkRateLimit()
Expand All @@ -130,10 +241,18 @@ export async function sendGroupedNotifications(groupedNotifications) {
body: JSON.stringify(emailContent),
})

const respText = await response.text().catch(() => "<no body>")
if (!response.ok) {
console.error(`Failed to send email to ${email}:`, response.statusText)
console.error(
`Failed to send email to ${email}: ${response.status} ${response.statusText} — ${respText}`
)
} else {
console.log(`Email sent successfully to ${email}`)
console.log(
`Email sent successfully to ${email}: ${response.status} — ${respText.substring(
0,
120
)}...`
)
}

emailCount++ // Increment the email count after sending each email
Expand All @@ -144,13 +263,18 @@ export async function sendGroupedNotifications(groupedNotifications) {
console.error(`Error sending email to ${email}:`, error)
}
}

console.log(`[Mailer] Processed ${allEmails.size} recipients.`)
}

// Function to fetch and send daily notifications
async function sendDailyNotifications() {
try {
const groupedNotifications = await fetchAndGroupNotifications()
await sendGroupedNotifications(groupedNotifications)
const [groupedNotifications, groupedOverdues] = await Promise.all([
fetchAndGroupNotifications(),
fetchAndGroupOverdueTasks(),
])
await sendGroupedNotifications(groupedNotifications, groupedOverdues)
} catch (error) {
console.error("Error in sendDailyNotifications:", error)
}
Expand Down
3 changes: 3 additions & 0 deletions db/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
9 changes: 9 additions & 0 deletions db/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ enum Status {
NOT_COMPLETED
}

enum AutoAssignNew {
NONE
CONTRIBUTOR
TEAM
ALL
}

model Task {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
Expand Down Expand Up @@ -200,6 +207,8 @@ model Task {
assignedMembers ProjectMember[] @relation("AssignedTasks")
elementId Int?
element Element? @relation(fields: [elementId], references: [id])
autoAssignNew AutoAssignNew @default(NONE)
anonymous Boolean @default(false)
}

model TaskLog {
Expand Down
4 changes: 2 additions & 2 deletions integrations/emails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ alt="STAPLE Logo" height="200"></center>
<h3>STAPLE Password Change</h3>

This email is to notify you that you recently updated your
password. If you did not make this change, please
password at https://app.staple.science. If you did not make this change, please
contact us immediately.
<p>
If you need more help, you can reply to this email to create a ticket.
Expand Down Expand Up @@ -289,7 +289,7 @@ alt="STAPLE Logo" height="200"></center>
<h3>STAPLE Profile Change</h3>

This email is to notify you that you recently updated your
profile information. If you did not make this change, please
profile information at https://app.staple.science. If you did not make this change, please
contact us immediately.
<p>
If you need more help, you can reply to this email to create a ticket.
Expand Down
3 changes: 3 additions & 0 deletions integrations/mailer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import nodemailer from "nodemailer"
import * as aws from "@aws-sdk/client-ses"
import { Resend } from "resend"

// use for Gmail
export async function Mailer(msg) {
const pass = process.env.EMAIL_PASS

Expand All @@ -20,6 +21,7 @@ export async function Mailer(msg) {
})
}

// use for Amazon
export async function Amazon(msg) {
const ses = new aws.SES({
apiVersion: "2010-12-01",
Expand All @@ -42,6 +44,7 @@ export async function Amazon(msg) {
}
}

// use for Resend
const resend = new Resend(process.env.RESEND_API_KEY)

export async function ResendMsg(msg) {
Expand Down
2 changes: 2 additions & 0 deletions mailers/forgotPasswordMailer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ export async function forgotPasswordMailer({ to, token }: ResetPasswordMailer) {

//send the email
await ResendMsg(createForgotPasswordMsg(to, resetUrl))
// await Amazon(createForgotPasswordMsg(to, resetUrl)) # or amazon
// await Mailer(createForgotPasswordMsg(to, resetUrl)) # or gmail
}
Loading