Skip to content

feat: Add support for scheduled posts #2222

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

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

m0wer
Copy link
Contributor

@m0wer m0wer commented Jun 12, 2025

Description

This PR implements scheduled posts functionality, allowing users to schedule content for future publication using the @schedule in X time syntax.

Closes #740

Core Features:

  • @schedule in X time command parsing (supports seconds, minutes, hours, days, weeks, months, years)
  • Database schema extensions: scheduledAt field on Item model with proper indexing
  • Database-level filtering for scheduled posts visibility (visible only to owners until published)
  • scheduledItems query for users to view their scheduled content (not exposed on the UI)

Technical Implementation:

  • Backend: Extended item creation/update flows to detect schedule commands and set scheduling metadata
  • Database: Added indexes for efficient querying and updated all stored procedures to filter scheduled posts from public view using COALESCE("Item"."scheduledAt", "Item"."invoicePaidAt", "Item".created_at) for timestamp handling
  • Security: Scheduled posts are hidden from public feeds until their scheduled time, visible only to the author
  • GraphQL: Extended schema with new fields, updated fragments to include scheduling data
  • UI: “time since” correctly displays scheduledAt for published scheduled posts
  • Efficient Architecture: Uses database-level filtering rather than background job processing for better performance and consistency

User Experience:

  • Posts with @schedule in 2 hours syntax are automatically scheduled
  • Scheduled posts maintain all original functionality (mentions, notifications, etc.) when their time arrives
  • Clean separation between scheduled and published content in all queries
  • Proper timestamp display showing when content was actually meant to be published
  • No background job overhead - scheduling handled efficiently through database queries

Screenshots

anon_view

Note that anon users can see that this account has 6 total items but only 5 are listed (one scheduled post not shown but counted).

Checklist

Are your changes backwards compatible? Please answer below:

Yes. The implementation is fully backwards compatible:

  • New database fields use default values and proper fallbacks
  • All existing queries continue to work unchanged
  • No breaking changes to existing API contracts
  • Schedule commands are optional and don't affect normal posting flow

On a scale of 1-10 how well and how have you QA'd this change and any features it might affect? Please answer below:

9/10. Comprehensive testing includes:

  • Schedule command parsing with various time formats
  • Items are not visible for other users or anons when scheduled
  • Posts become visible when the scheduling time is due
  • Proper timestamp display for published scheduled posts
  • Database consistency and efficient query performance

For frontend changes: Tested on mobile, light and dark mode? Please answer below:

No frontend changes.

Did you introduce any new environment variables? If so, call them out explicitly here:

No new environment variables introduced. The feature uses existing database infrastructure with efficient query-based filtering.

TODO

Figure out a way to refresh the item's "created at" time that's cached by Apollo and does not get updated even after the scheduled publishing happens.

RESOLVED: Implemented proper Apollo cache handling and timestamp display logic. Scheduled posts now show their scheduledAt timestamp in the UI once published, providing users with the intended publication time rather than the original creation time.

@huumn
Copy link
Member

huumn commented Jun 12, 2025

Figure out a way to refresh the item's “created at” time that's cached by Apollo and does not get updated even after the scheduled publishing happens. Any ideas?

We do something similar with invoicePaidAt for items where we display the payment time as the creation time. If you have trouble finding it let me know, but you'll likely need some COALESCE(scheduledAt, invoicePaidAt, createdAt) as createdAt in a few places and possibly a patch to the createdAt resolver.

m0wer added 3 commits June 13, 2025 22:12
Database Schema Changes

- Removed isScheduled boolean field from Item model
- Kept only scheduledAt timestamp field
- Removed unnecessary indexes for isScheduled
- Updated stored functions to use scheduledAt IS NULL instead of isScheduled = false

API Changes

- Updated scheduledOrMine() function to check scheduledAt IS NULL
- Updated scheduled items query to use scheduledAt IS NOT NULL
- Updated mutations to only set/unset scheduledAt field
- Kept isScheduled as computed GraphQL field that returns !!item.scheduledAt

Item Creation Logic

- Removed isScheduled variable and logic
- Only set scheduledAt field during item creation and updates

Worker Functions

- Updated to check !item.scheduledAt instead of !item.isScheduled
- Only update scheduledAt field when publishing/canceling

The refactoring maintains the same functionality while eliminating the redundant boolean field.
The isScheduled GraphQL field remains available for frontend convenience as a computed property.
@m0wer
Copy link
Contributor Author

m0wer commented Jun 17, 2025

I've been struggling with the time update thing for a few days but I'm getting closer. We can't just do the same as with the invoice paid logic because scheduled posts run in the backend and don't have access to the apollo cache directly. I guess we'll need some kind of cache update system where the frontend polls the backend for these kind of updates.

Open for ideas though.

This makes things cleaner and shows the right “time since” for scheduled
posts.
@m0wer
Copy link
Contributor Author

m0wer commented Jun 23, 2025

Finally fixed it! Had to change a bit the implementation, it's more clear now and “time since” works as expected.

@m0wer
Copy link
Contributor Author

m0wer commented Jun 23, 2025

The commits can (and probably should) be squashed.

@m0wer m0wer marked this pull request as ready for review June 23, 2025 20:00
@m0wer m0wer requested a review from huumn June 23, 2025 20:01
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

@Soxasora Soxasora self-requested a review July 9, 2025 20:50
Copy link
Member

@Soxasora Soxasora left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can schedule a post, but it doesn't take into account all the side effects that come with posting.

If you mention someone, we store this in the Mention table with the relative itemId. The problem is, the scheduled Item is excluded from queries until it's live, but notifications don’t know about it, try to access that Item, and it crashes the notifications page for the mentioned user.

The same applies for other types of notifications tied to an Item, like user subscriptions, territory subscriptions, and so on.

While scheduledOrMine can look like an easy way to do scheduled posts, it can become really messy really quickly.

Take another look ^^

Comment on lines +1478 to +1479
isScheduled: async (item, args, { me, models }) => {
return !!item.scheduledAt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you forgot isScheduled around the code, as you can now just do !!item.scheduledAt when you need it.

Comment on lines +141 to +142
<Link href={`/items/${item.id}`} title={item.scheduledAt || item.invoicePaidAt || item.createdAt} className='text-reset' suppressHydrationWarning>
{timeSince(new Date(item.scheduledAt || item.invoicePaidAt || item.createdAt))}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nitpick: I think it would be better UX if the countdown says "in 5m" rather than "5m". Or something else that signals that it's a scheduled, not-yet-live post.

Comment on lines +30 to +31
export const hasScheduleMention = (text) => scheduleMentionPattern.test(text ?? '')

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to add this to lib/form.js to toast a successful scheduling?

@@ -733,6 +749,36 @@ export default {
homeMaxBoost: homeAgg._max.boost || 0,
subMaxBoost: subAgg?._max.boost || 0
}
},
scheduledItems: async (parent, { cursor, limit = LIMIT }, { me, models }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you used this anywhere. It was a good direction though, it would've been nice to access a list of my scheduled posts.

note: I didn't look much into this query

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Scheduled posts
3 participants