How Packing Lists Are Managed with Templates, Member Assignments, and Bag Tracking in TREK

TREK implements packing lists through a relational architecture that separates items, bags, and member assignments into distinct database schemas, with reusable templates stored as hierarchal collections and all mutations protected by the packing_edit permission.

The TREK travel planning application organizes trip preparation through a sophisticated packing list management system. This architecture, implemented in the mauriceboe/TREK repository, uses Zod schemas to define strict data shapes for items and bags while a centralized service layer handles member assignments and template workflows.

Core Database Schema

The system defines four interconnected entities in shared/src/packing/packing.schema.ts that establish the foundation for packing list management.

Packing Items

Individual checklist entries are governed by the packingItemSchema (lines 17-35), which specifies fields for name, category, quantity, a boolean checked flag, optional weight, and a bag_id foreign key. This schema ensures that every item tracks its completion status and physical container assignment.

Bags as Logical Containers

The packingBagSchema (lines 48-66) defines bags as color-coded containers with properties for name, color, and sort_order. Each bag belongs to a specific trip and serves as a grouping mechanism for items, enabling teams to organize gear by physical luggage or responsibility zones.

Member Assignments

Bag access control operates through the packingBagMemberSchema (lines 37-45), which creates a many-to-many junction table between users and bags. This schema enables the UI to render which team members are responsible for specific bags and restricts edit permissions to assigned users.

Template Storage

Reusable checklists are structured by the packingTemplateSummarySchema (lines 112-127) and related template item schemas. Templates store read-only collections of items grouped by categories, allowing users to apply predefined packing lists to new trips without recreating common entries.

Service Layer Implementation

The server/src/services/packingService.ts file encapsulates all business logic for packing operations, providing atomic functions for CRUD actions and permission enforcement.

Item and Bag Operations

The listItems(tripId) function executes a SQL query selecting all rows from packing_items ordered by sort_order ASC and created_at ASC (lines 10-13). For creating new entries, createItem(tripId, data) calculates the next sort index and sanitizes quantity inputs to a 1-999 range before insertion (lines 16-24).

When retrieving baggage containers, listBags(tripId) (lines 121-137) queries the packing_bags table and executes a secondary fetch against packing_bag_members to populate each bag's members array with user data including avatar URLs.

Dynamic Member Management

Assigning users to specific bags utilizes addBagMember(bagId, userId) (lines 144-149), which executes an INSERT OR IGNORE INTO packing_bag_members statement to prevent duplicate relationships. The inverse operation, removeBagMember(bagId, userId) (lines 140-143), deletes the specific user-bag association row when a member is unassigned from luggage responsibilities.

Template Workflows

Templates provide reusable packing patterns that teams can apply across multiple trips, with creation and application handled by distinct service functions.

Saving Trip Data as Templates

The saveAsTemplate(tripId, userId, name) function (lines 241-267) transforms a trip's current packing list into a reusable template. This process inserts a parent record into packing_templates, then iterates through categories to populate packing_template_categories, and finally writes individual items to packing_template_items while preserving the original grouping structure.

Applying Templates to Trips

When users select a template, applyTemplate(tripId, templateId) (lines 214-230) clones the template's contents into the target trip. The function queries packing_template_items with a SELECT statement, then loops through results to create fresh rows in packing_items with the new trip_id, returning null if the source template contains no items.

Template access is bifurcated by role: regular members receive read-only summaries via listTemplates(tripId, isMember) (lines 199-207), while administrators manipulate template records through adminService.ts endpoints.

Permission Model and Security

All packing mutations are gated by the packing_edit permission defined in server/src/services/permissions.ts (line 42). Before executing any create, update, or delete operation, the service layer invokes verifyTripAccess (imported from server/src/services/tripAccess.ts) to validate that the requesting user is either the trip owner or a member explicitly granted packing rights.

This permission check applies to item modifications, bag creation, member assignments, and template applications, ensuring that unauthorized users cannot alter packing lists even if they have general trip access.

Practical Implementation Examples

The following patterns demonstrate how client applications interact with the packing system:

// Load all packing items and bags for a trip
const items = await fetch(`/api/trips/${tripId}/packing/items`).then(r => r.json());
const bags = await fetch(`/api/trips/${tripId}/packing/bags`).then(r => r.json());

// Create a new packing item with category and quantity
await fetch(`/api/trips/${tripId}/packing/items`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ 
    name: 'Sunscreen', 
    category: 'Toiletries', 
    quantity: 2 
  })
});

// Assign a team member to a specific bag
await fetch(`/api/trips/${tripId}/packing/bags/${bagId}/members`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ userId: teammateId })
});

// Save current trip's packing list as a reusable template
await fetch(`/api/trips/${tripId}/packing/templates`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Winter Iceland Gear' })
});

// Apply a template to populate the current trip
await fetch(`/api/trips/${tripId}/packing/templates/${templateId}/apply`, { 
  method: 'POST' 
});

Summary

  • TREK separates packing concerns into four distinct schemas: items, bags, bag-members, and templates, defined in shared/src/packing/packing.schema.ts.
  • Bag tracking combines the packing_bags table for container metadata with the packing_bag_members junction table to manage user assignments and edit permissions.
  • Templates are created via saveAsTemplate which writes to three hierarchical tables, and applied via applyTemplate which clones items into the target trip.
  • Security is enforced through the packing_edit permission checked by verifyTripAccess before any mutation, ensuring only owners and assigned members can modify packing data.

Frequently Asked Questions

How are bag permissions structured in TREK?

Bag permissions operate through a separate membership table rather than item-level checks. The packing_bag_members table links user IDs to specific bag IDs, and the service layer loads these relationships when fetching bags. This allows the UI to display assigned members with avatars while the backend ensures only assigned users can modify items within that bag.

Can regular trip members create packing templates?

No. According to the permission matrix in server/src/services/permissions.ts, regular members receive read-only access to templates through the listTemplates function. Template creation, modification, and deletion are restricted to administrators and trip owners who access these functions through adminService.ts endpoints.

What happens when a template is applied to an existing trip?

The applyTemplate function creates entirely new rows in packing_items for the target trip, copying data from packing_template_items but generating fresh IDs and timestamps. It preserves category groupings from the template but does not delete existing items in the trip, effectively merging the template contents with the current packing list.

How does the system handle duplicate member assignments?

The addBagMember function uses an INSERT OR IGNORE SQL pattern (line 144-149 in packingService.ts) that silently skips duplicate entries if the user-bag relationship already exists. This prevents database constraint violations while allowing idempotent assignment operations from the client side.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →