calendar

Events and attendees with optional polymorphic links to CRM records.

Calendar

Events with attendees, supporting all-day events, recurrence rules (iCal RRULE), and optional polymorphic links to contacts, companies, deals, tickets, or projects. The event_attendees table intentionally has no foreign key constraint on contact_id, so the calendar skill works without the CRM skill installed.

Tables

events

ColumnTypeDescription
iduuidPrimary key, auto-generated.
org_iduuidReferences organizations. Cascade delete.
creator_iduuidReferences users. Set NULL on user delete.
entity_typetextOptional polymorphic link type. Constrained to: contact, company, deal, ticket, project.
entity_iduuidID of the linked record.
titletextEvent title.
descriptiontextOptional event description.
locationtextOptional location string.
statusevent_statusEvent state. Defaults to confirmed.
starts_attimestamptzEvent start time.
ends_attimestamptzEvent end time.
all_daybooleanWhether this is an all-day event. Defaults to false.
recurrence_ruletextiCal RRULE string for recurring events.
created_attimestamptzRow creation timestamp.
updated_attimestamptzAuto-updated via trigger.
metadatajsonbArbitrary key-value data. Defaults to empty object.

event_attendees

ColumnTypeDescription
iduuidPrimary key, auto-generated.
org_iduuidReferences organizations. Cascade delete.
event_iduuidReferences events. Cascade delete.
user_iduuidReferences users. Cascade delete. At least one of user_id or contact_id is required.
contact_iduuidReferences contacts if CRM is installed. No FK constraint. At least one of user_id or contact_id is required.
responseattendee_responseRSVP status. Defaults to pending.
is_organizerbooleanWhether this attendee is the event organizer. Defaults to false.
created_attimestamptzRow creation timestamp.
updated_attimestamptzAuto-updated via trigger.
metadatajsonbArbitrary key-value data. Defaults to empty object.

Enums

event_status

ValueDescription
confirmedEvent is confirmed and will take place.
tentativeEvent is tentatively scheduled.
cancelledEvent has been cancelled.

attendee_response

ValueDescription
acceptedAttendee has accepted the invitation.
declinedAttendee has declined the invitation.
tentativeAttendee has tentatively accepted.
pendingAttendee has not yet responded.

Row-Level Security

Both tables are scoped to the current user's organization via get_user_org_id(). Any org member can select, insert, and update rows. Only admins (checked via is_admin()) can delete events or attendees.

Dependencies

  • identity -- organizations, users, get_user_org_id(), is_admin(), set_updated_at()

Example Queries

Fetch upcoming events for a specific user:

SELECT
  e.title,
  e.starts_at,
  e.ends_at,
  e.location,
  ea.response
FROM events e
JOIN event_attendees ea ON ea.event_id = e.id
WHERE ea.user_id = '<user_id>'
  AND e.starts_at > now()
  AND e.status != 'cancelled'
ORDER BY e.starts_at ASC
LIMIT 20;

List all attendees for a given event, showing both internal users and external contacts:

SELECT
  ea.id,
  ea.response,
  ea.is_organizer,
  u.full_name AS user_name,
  ea.contact_id
FROM event_attendees ea
LEFT JOIN users u ON u.id = ea.user_id
WHERE ea.event_id = '<event_id>'
ORDER BY ea.is_organizer DESC, u.full_name ASC;

Find events within a date range (e.g. a calendar week view):

SELECT
  id,
  title,
  starts_at,
  ends_at,
  all_day,
  status,
  location
FROM events
WHERE starts_at < '<range_end>'::timestamptz
  AND ends_at > '<range_start>'::timestamptz
  AND status != 'cancelled'
ORDER BY starts_at ASC;

Count events per day for the current month:

SELECT
  date_trunc('day', starts_at)::date AS event_date,
  count(*) AS event_count
FROM events
WHERE starts_at >= date_trunc('month', now())
  AND starts_at < date_trunc('month', now()) + interval '1 month'
  AND status != 'cancelled'
GROUP BY event_date
ORDER BY event_date;

List events linked to a specific CRM record (e.g. a deal):

SELECT
  id,
  title,
  starts_at,
  ends_at,
  status,
  creator_id
FROM events
WHERE entity_type = 'deal'
  AND entity_id = '<deal_id>'
ORDER BY starts_at DESC;