All files / web/src/db/schema session-observation-shares.ts

100% Statements 57/57
100% Branches 3/3
100% Functions 2/2
100% Lines 57/57

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 582x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 39x 39x 39x 39x 39x 2x 2x 2x 2x  
import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
import { players } from './players'
import { sessionPlans } from './session-plans'
 
/**
 * Shareable observation links for practice sessions
 *
 * Allows parents/teachers to share time-limited links that anyone can use
 * to observe a student's practice session without logging in.
 */
export const sessionObservationShares = sqliteTable(
  'session_observation_shares',
  {
    // 10-char base62 token (cryptographically random)
    id: text('id').primaryKey(),
 
    // Session being shared
    sessionId: text('session_id')
      .notNull()
      .references(() => sessionPlans.id, { onDelete: 'cascade' }),
 
    // Player being observed (denormalized for fast lookup)
    playerId: text('player_id')
      .notNull()
      .references(() => players.id, { onDelete: 'cascade' }),
 
    // Who created the share link
    createdBy: text('created_by').notNull(),
 
    // Timestamps
    createdAt: integer('created_at', { mode: 'timestamp' })
      .notNull()
      .$defaultFn(() => new Date()),
 
    expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
 
    // Status: active, expired (time-based), or revoked (manually)
    status: text('status', {
      enum: ['active', 'expired', 'revoked'],
    })
      .notNull()
      .default('active'),
 
    // Analytics
    viewCount: integer('view_count').notNull().default(0),
    lastViewedAt: integer('last_viewed_at', { mode: 'timestamp' }),
  },
  (table) => ({
    // Index for cleanup when session ends
    sessionIdx: index('idx_session_observation_shares_session').on(table.sessionId),
    // Index for listing active shares
    statusIdx: index('idx_session_observation_shares_status').on(table.status),
  })
)
 
export type SessionObservationShare = typeof sessionObservationShares.$inferSelect
export type NewSessionObservationShare = typeof sessionObservationShares.$inferInsert