Skip to content

Journeys & Streaks

The Journeys system tracks user activity streaks and daily activity logs using Cloudflare Workers KV storage. This service provides fast, distributed access to user streak data without requiring Mantle2 database queries.

Architecture

  • Storage: Cloudflare Workers KV (key-value storage)
  • Key Format: journey:{user_id}:{date} (e.g., journey:000000000000000123456789:2025-01-10)
  • TTL: Journey records persist indefinitely to maintain historical streak data
  • Caching: KV provides automatic edge caching

Data Model

Each journey record is stored as JSON in KV:

json
{
	"user_id": "000000000000000123456789",
	"date": "2025-01-10",
	"streak": 7,
	"activities": ["000000000000001234567890", "000000000000001234567891"],
	"created_at": "2025-01-10T08:00:00Z",
	"updated_at": "2025-01-10T18:30:00Z"
}

Journey Fields

FieldTypeDescription
user_idstringUser ID (24-digit zero-padded)
datestringJourney date (YYYY-MM-DD)
streaknumberCurrent consecutive day streak
activitiesarrayActivity IDs logged for this day (24-digit strings)
created_atstringISO 8601 timestamp when journey started
updated_atstringISO 8601 timestamp of last update

Streak Calculation

Streaks are calculated by checking consecutive days of activity:

  1. User completes an activity on Day N
  2. Check if previous day (N-1) has a journey record
  3. If yes, increment streak: previous_streak + 1
  4. If no, reset streak to: 1

Streak Rules

  • Minimum 1 activity per day to maintain streak
  • Midnight UTC is the day boundary
  • Missing a single day resets streak to 0
  • Streaks can be manually reset via API

Endpoints

GET /users/:id/journey

Retrieve current journey and streak information for a user.

Path Parameters

NameTypeDescription
idstringUser ID (24-digit string)

Query Parameters

NameTypeDescription
datestringSpecific date (YYYY-MM-DD), defaults to today

Responses

200 OK:

json
{
	"user_id": "000000000000000123456789",
	"date": "2025-01-10",
	"streak": 7,
	"activities": [
		"000000000000001234567890",
		"000000000000001234567891",
		"000000000000001234567892"
	],
	"milestones": {
		"longest_streak": 14,
		"total_days": 42,
		"current_month_days": 10
	},
	"created_at": "2025-01-10T08:00:00Z",
	"updated_at": "2025-01-10T19:45:00Z"
}

404 Not Found — no journey found for date

401 Unauthorized — authentication required

POST /users/:id/journey/increment

Increment the daily streak. Idempotent per day — calling multiple times on same day does not increase streak.

Path Parameters

NameTypeDescription
idstringUser ID (24-digit string)

Request Body

json
{
	"date": "2025-01-10"
}

If date is omitted, uses current UTC date.

Responses

200 OK:

json
{
	"user_id": "000000000000000123456789",
	"date": "2025-01-10",
	"streak": 8,
	"activities": [],
	"created_at": "2025-01-10T08:00:00Z",
	"updated_at": "2025-01-10T08:00:00Z"
}

401 Unauthorized

POST /users/:id/journey/reset

Reset user's streak to zero. Clears current streak counter but preserves historical journey records.

Path Parameters

NameTypeDescription
idstringUser ID (24-digit string)

Responses

200 OK:

json
{
	"user_id": "000000000000000123456789",
	"date": "2025-01-10",
	"streak": 0,
	"activities": [],
	"message": "Streak reset successfully"
}

401 Unauthorized

404 Not Found — user has no active journey

POST /users/:id/journey/activities/:activity_id

Add an activity to today's journey. Ensures uniqueness — adding the same activity multiple times on the same day has no effect.

Path Parameters

NameTypeDescription
idstringUser ID (24-digit string)
activity_idstringActivity ID (24-digit string)

Request Body

Optional:

json
{
	"date": "2025-01-10"
}

If date is omitted, uses current UTC date.

Responses

200 OK:

json
{
	"user_id": "000000000000000123456789",
	"date": "2025-01-10",
	"streak": 7,
	"activities": [
		"000000000000001234567890",
		"000000000000001234567891",
		"000000000000001234567892",
		"000000000000001234567893"
	],
	"created_at": "2025-01-10T08:00:00Z",
	"updated_at": "2025-01-10T20:15:00Z"
}

401 Unauthorized

404 Not Found — user or activity not found

409 Conflict — activity already added to this day's journey

GET /users/:id/journey/history

Retrieve journey history for a user over a date range.

Path Parameters

NameTypeDescription
idstringUser ID (24-digit string)

Query Parameters

NameTypeDescription
start_datestringStart date (YYYY-MM-DD), defaults to 30 days ago
end_datestringEnd date (YYYY-MM-DD), defaults to today

Responses

200 OK:

json
{
	"user_id": "000000000000000123456789",
	"start_date": "2024-12-11",
	"end_date": "2025-01-10",
	"journeys": [
		{
			"date": "2025-01-10",
			"streak": 7,
			"activities": ["000000000000001234567890", "000000000000001234567891"],
			"created_at": "2025-01-10T08:00:00Z",
			"updated_at": "2025-01-10T20:15:00Z"
		},
		{
			"date": "2025-01-09",
			"streak": 6,
			"activities": ["000000000000001234567890"],
			"created_at": "2025-01-09T09:30:00Z",
			"updated_at": "2025-01-09T09:30:00Z"
		}
	],
	"total_days": 2
}

401 Unauthorized

Milestones

The system tracks several milestone metrics:

MilestoneDescription
longest_streakHighest consecutive day streak achieved
total_daysTotal number of days with any activity
current_month_daysDays active in current calendar month
total_activitiesTotal activities logged across all days

Semantics

Increment Idempotency

Calling POST /journey/increment multiple times on the same day is safe:

  • First call: creates journey record or increments streak
  • Subsequent calls: returns existing journey, no streak change

Activity Uniqueness

Adding the same activity multiple times to the same day's journey:

  • First call: adds activity to array
  • Subsequent calls: returns 409 Conflict, no duplicate added

Streak Preservation

Journey history is preserved even after streak resets, allowing users to view their complete activity history.

Integration

  • Mantle2: Journey service reads activity data from Mantle2 API to validate activity IDs
  • Crust: Frontend displays journey calendar and streak badges
  • Scheduled Tasks: Daily job checks for broken streaks and sends notifications

Errors

Standard error response:

json
{
	"error": {
		"code": "E404",
		"message": "Journey not found for date"
	}
}

Common status codes:

  • 200 OK
  • 401 Unauthorized — authentication required
  • 404 Not Found — user or activity not found
  • 409 Conflict — duplicate activity on same day
  • 429 Too Many Requests — rate limit exceeded