Référence du Schéma Firestore
Modèles de données Firestore et collections complètes pour la plateforme TeamDay.
Vue d’Ensemble des Collections
TeamDay utilise Firebase Firestore comme base de données principale. Le schéma suit une structure hiérarchique avec des règles de sécurité strictes et des abonnements en temps réel.
| Collection | Objectif | Contrôle d’Accès |
|---|---|---|
organizations | Gestion des organisations/équipes | Membres uniquement |
users | Données de compte utilisateur | Propriétaire uniquement |
userProfiles | Préférences et paramètres utilisateur | Propriétaire uniquement |
character | Agents/assistants IA | Basé sur la visibilité |
spaces | Espaces de travail pour les agents | Basé sur la visibilité |
chats | Conversations | Propriétaire/membres org |
tasks | Tâches créées par les agents | Membres org |
executions | Historique d’exécution des agents | Membres org |
missions | Tâches autonomes de longue durée | Membres org |
scheduledTasks | Automatisations d’agents planifiées | Membres org |
skills | Packages de prompts réutilisables | Basé sur la visibilité |
subagents | Configurations d’agents spécialisés | Basé sur la visibilité |
plugins | Packages de configuration | Basé sur la visibilité |
pluginInstallations | Installations de plugins par espace | Membres org |
mcpInstances | Instances de serveur MCP | Membres org |
personal_access_tokens | Tokens API | Propriétaire uniquement |
invites | Invitations d’organisation | Lecture publique |
usageAggregates | Métriques d’utilisation | Membres org |
creditTransactions | Historique de solde de crédits | Membres org |
Collections Principales
organizations
Gestion des organisations/équipes. Chaque utilisateur appartient à une ou plusieurs organisations.
interface Organization {
id: string
name: string
logoUrl?: string
ownerId: string // User who created the org
members: string[] // Array of user IDs
subscription?: OrganizationSubscription
createdAt: Timestamp
updatedAt: Timestamp
// Encrypted fields (server-side only)
encryptedAnthropicApiKey?: string // Encrypted API key
}
Exemple de Document:
{
"id": "org_abc123",
"name": "Acme Corporation",
"logoUrl": "https://storage.googleapis.com/...",
"ownerId": "user_xyz",
"members": ["user_xyz", "user_abc", "user_def"],
"subscription": {
"tier": "pro",
"status": "active"
},
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
Index Requis:
members(array-contains) pour les requêtes de membresownerIdpour les requêtes de propriétaire
Sous-collections:
gitCredentials/{credentialId}- Tokens OAuth pour les intégrations Git
users
Authentification utilisateur et données de compte. Géré par Firebase Auth.
interface User {
id: string // Firebase Auth UID
email: string
displayName?: string
photoURL?: string
createdAt: Timestamp
lastSignIn?: Timestamp
}
Sécurité: Accès propriétaire uniquement. Suppression côté client non autorisée.
Sous-collections:
gitCredentials/{credentialId}- Tokens OAuth Git personnels
userProfiles
Préférences utilisateur, paramètres et dernière organisation utilisée.
interface UserProfile {
userId: string
lastUsedOrganizationId?: string
preferences?: {
theme?: 'light' | 'dark' | 'system'
locale?: string
}
createdAt: Timestamp
updatedAt?: Timestamp
// Encrypted fields (server-side only)
encryptedAnthropicApiKey?: string
encryptedClaudeCodeOAuthToken?: string
}
Sécurité: Le propriétaire peut lire/mettre à jour. Ne peut pas modifier les champs d’identification chiffrés depuis le client.
character (Agents IA)
Agents/assistants IA créés et configurés par les utilisateurs.
type CharacterVisibility = 'private' | 'organization' | 'public' | 'unlisted'
type CharacterCategory = 'marketing' | 'finance' | 'hr' | 'engineering' | 'operations' | 'general'
interface Character {
id: string
name: string
role: string // e.g., "Marketing Assistant"
initialGreeting: string
characterDescription: string
system_message: string // System prompt
systemPrompt?: string // Alias for system_message
image: string // Avatar URL
color: string // Brand color
// Configuration
model?: string // e.g., 'claude-sonnet-4-6'
visibility: CharacterVisibility
ownerId: string
organizationId?: string
ownerName?: string
// Resources
files: any[] // Attached files
linksToGetContextFrom: any[] // URLs for context
tools?: string[] | null // Legacy AI tools
advanced_tools?: string[] // MCP tool IDs
skillIds?: string[] // Skill references
subagentIds?: string[] // Subagent references
// Features
suggested_topics?: any[]
suggested_topics_conversation_starters?: string[]
voice?: string // Voice model ID
tags?: string[]
articles?: string[]
// Template system
isTemplate?: boolean // Global template flag
category?: CharacterCategory
featured?: boolean
version?: number
changelog?: string
templateId?: string // Source template ID
instanceCount?: number // For templates
// State
isForkable?: boolean
archived?: boolean
// Statistics
statistics?: {
likes: number
dislikes: number
uniqueChats: number
}
// Timestamps
createdAt: Date | Timestamp
updatedAt?: Date | Timestamp
// Soft delete
deletedAt?: Date | null
isDeleted?: boolean
}
Exemple de Document:
{
"id": "char_123",
"name": "Marketing Maven",
"role": "Marketing Assistant",
"initialGreeting": "Hi! I'm here to help with marketing tasks.",
"characterDescription": "Expert in content marketing and social media",
"system_message": "You are a marketing specialist...",
"image": "https://storage.googleapis.com/avatars/marketing.png",
"color": "#FF6B6B",
"model": "claude-sonnet-4-6",
"visibility": "organization",
"ownerId": "user_xyz",
"organizationId": "org_abc123",
"skillIds": ["research-assistant", "technical-writer"],
"advanced_tools": ["mcp_google_analytics"],
"tags": ["marketing", "content"],
"archived": false,
"isTemplate": false,
"createdAt": "2024-01-15T10:00:00Z"
}
Index Requis:
- Composite:
organizationId+isTemplate+createdAt(desc) ownerId+visibilityisTemplatepour les requêtes de template
Règles de Visibilité:
public: Visible par tous les utilisateurs authentifiésorganization: Visible par tous les membres de l’organisationprivate: Seul le propriétaire peut voirunlisted: Seul le propriétaire peut voir (pas dans les listes)- Templates (
isTemplate: true): Visible par tous
spaces
Espaces de travail où les agents opèrent. Chaque espace a son propre système de fichiers et configuration git.
type SpaceVisibility = 'private' | 'organization' | 'public'
type SpaceInitializationType = 'empty' | 'readme' | 'git' | 'starterKit'
type SpaceInitializationStatus = 'pending' | 'initializing' | 'ready' | 'failed'
interface Space {
id: string
organizationId: string
name: string
description: string
// Branding
coverImage?: string
gradientColors?: [string, string] // [fromColor, toColor]
coverImageAttribution?: {
source: 'unsplash' | 'upload' | 'url' | 'ai'
photographerName?: string
photographerUsername?: string
unsplashUrl?: string
}
// Access control
visibility: SpaceVisibility
ownerId: string
allowedUserIds?: string[] // For private spaces
// Configuration
assignedAgents?: string[] // Character IDs
instructions?: string // Fallback instructions
advanced_tools?: string[] // MCP instance IDs
skillIds?: string[] // Skill IDs
subagentIds?: string[] // Subagent IDs
pluginIds?: string[] // Plugin IDs to install
// Git configuration
git?: {
aiSettings?: {
autoCommitOnSessionEnd?: boolean
generateCommitMessages?: boolean
autoResolveConflicts?: boolean
requireApprovalForPush?: boolean
}
permissions?: Record<string, {
readers: string[]
writers: string[]
pushers: string[]
defaultPermission: 'none' | 'read' | 'write' | 'push'
}>
defaultUserId?: string
remotes?: Record<string, string> // Cache only
}
// Initialization
initializationStatus?: SpaceInitializationStatus
initializationType?: SpaceInitializationType
initializationError?: string
// Statistics
chatCount?: number
fileCount?: number
// Timestamps
createdAt: Date
updatedAt: Date
// Soft delete
deletedAt?: Date | null
isDeleted?: boolean
// Optimistic UI
isOptimistic?: boolean
clientSideId?: string
}
Exemple de Document:
{
"id": "space_456",
"organizationId": "org_abc123",
"name": "Marketing Campaigns",
"description": "Space for marketing campaign work",
"visibility": "organization",
"ownerId": "user_xyz",
"assignedAgents": ["char_123"],
"skillIds": ["research-assistant"],
"gradientColors": ["#3b82f6", "#8b5cf6"],
"initializationStatus": "ready",
"initializationType": "empty",
"chatCount": 5,
"fileCount": 23,
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T12:00:00Z"
}
Index Requis:
organizationId+updatedAt(desc)ownerIdpour les requêtes de propriétairevisibilitypour le filtrage
chats
Conversations entre les utilisateurs et les agents.
type ChatStatus = 'standby' | 'working' | 'done' | 'needs_attention' | 'error'
interface Chat {
id: string
organizationId: string
spaceId: string
title: string
subtitle?: string
status: ChatStatus
ownerId: string
lastMessage?: string
sessionId?: string // Claude Code session ID
// Session lifecycle
closed: boolean
closedAt?: Date | null
// Visibility
visibility?: 'private' | 'organization' | 'public'
// Timestamps
createdAt: Date
updatedAt: Date
}
Sous-collections:
messages/{messageId}- Messages de chat (immuables)
Structure des Messages:
interface ChatMessage {
id: string
chatId: string
role: 'user' | 'assistant'
content: string
createdAt: Date
sessionId?: string
toolCalls?: Array<{
name: string
input: any
result?: string
}>
}
Index Requis:
- Composite:
organizationId+spaceId+updatedAt(desc) ownerIdpour les chats de l’utilisateursessionIdpour les requêtes de session
Sécurité: Les messages sont immuables (pas de mise à jour/suppression).
tasks
Tâches créées par les agents pour la coordination et l’attribution.
type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'
type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'
interface Task {
id: string
organizationId: string
title: string
description: string
assignedTo?: string | null // Agent ID
priority: TaskPriority
status: TaskStatus
spaceId?: string | null
createdBy: string | null // Agent ID or user ID
// Timestamps
createdAt: string // ISO 8601
completedAt?: string | null
}
Index Requis:
- Composite:
organizationId+status+createdAt(desc) assignedTopour les requêtes d’attributionspaceIdpour les tâches d’espace
executions
Suivi de l’exécution des agents et historique de délégation.
type ExecutionStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
interface Execution {
id: string
organizationId: string
agentId: string
chatId: string
sessionId: string
status: ExecutionStatus
// Content
message: string // User's message
result?: string // Agent's response
error?: string // Error if failed
// Delegation tracking
parentExecutionId?: string
delegationDepth: number
// Timing
startedAt: Date
completedAt?: Date
duration?: number // milliseconds
// Cost tracking
tokensInput?: number
tokensOutput?: number
cost?: number
}
Index Requis:
- Composite:
organizationId+chatId+startedAt(desc) sessionIdpour les requêtes de sessionparentExecutionIdpour les chaînes de délégation
missions
Tâches d’agents autonomes de longue durée.
type MissionStatus = 'pending' | 'running' | 'paused' | 'completed' | 'failed'
type AgentType = 'claude' | 'gemini' | 'codex'
interface Mission {
id: string
userId: string
organizationId: string
spaceId?: string
title: string
goal: string
agentType: AgentType
// Schedule
schedule: {
type: 'once' | 'cron' | 'continuous'
value?: string // Cron expression
lastRun?: number
nextRun?: number
}
// State
state: {
currentStep: string
contextSummary: string
artifacts: string[]
memory: Record<string, any>
}
status: MissionStatus
logs: MissionLogEntry[]
// Timestamps
createdAt: number
updatedAt: number
}
interface MissionLogEntry {
timestamp: number
level: 'info' | 'warn' | 'error'
message: string
metadata?: any
}
Index Requis:
- Composite:
organizationId+status+createdAt(desc) userIdpour les missions utilisateurspaceIdpour les missions d’espace
scheduledTasks
Automatisations d’agents planifiées (tâches cron).
interface ScheduledTask {
id: string
organizationId: string
characterId: string // Agent to run
spaceId?: string // Optional space context
// Task details
name: string
prompt: string // What to ask the agent
schedule: string // Cron expression
// State
isActive: boolean
lastRun?: Date | null
lastRunStatus?: 'success' | 'error' | 'running' | null
lastRunSessionId?: string | null
nextRun?: Date | null
// Metadata
createdAt: Date
createdBy: string
updatedAt?: Date | null
}
Collection Associée: scheduledTaskExecutions/{executionId} stocke l’historique d’exécution.
Index Requis:
- Composite:
organizationId+isActive+nextRun(asc) characterIdpour les plannings d’agentscreatedBypour les requêtes de créateur
skills
Packages de prompts réutilisables qui étendent les capacités des agents.
type SkillVisibility = 'private' | 'organization' | 'public'
type SkillCategory = 'research' | 'writing' | 'data' | 'code' | 'marketing' | 'productivity' | 'file-handling' | 'other'
interface Skill {
id: string
// Identity
name: string // kebab-case (e.g., "research-assistant")
displayName: string // Human readable
description: string // When to invoke
// Content
prompt: string // SKILL.md markdown instructions
// Dependencies
builtInTools?: string[] // ["Read", "Write", "Bash", "WebSearch"]
requiredMCPs?: string[] // ["google-analytics", "bigquery"]
// Categorization
category: SkillCategory
tags?: string[]
icon?: string // Emoji or icon name
// Ownership
organizationId: string
ownerId: string
visibility: SkillVisibility
// Marketplace
featured?: boolean
verified?: boolean
usageCount?: number
rating?: number // 1-5
ratingCount?: number
// Versioning
version: number
changelog?: string
// Timestamps
createdAt: Date | Timestamp
updatedAt: Date | Timestamp
// Soft delete
isDeleted?: boolean
deletedAt?: Date | null
}
Exemple de Document:
{
"id": "skill_research",
"name": "research-assistant",
"displayName": "Research Assistant",
"description": "Deep research on a topic using web search",
"prompt": "# Research Assistant\n\nYou are a research specialist...",
"category": "research",
"icon": "🔍",
"builtInTools": ["WebSearch", "WebFetch", "Read", "Write"],
"organizationId": "org_abc123",
"ownerId": "user_xyz",
"visibility": "organization",
"version": 1,
"createdAt": "2024-01-15T10:00:00Z"
}
Index Requis:
- Composite:
organizationId+visibility+createdAt(desc) category+visibilitypour la marketplacefeaturedpour les skills en vedette
plugins
Packages de configuration (dépôts GitHub) qui regroupent des commandes, des agents, des skills et des serveurs MCP.
type PluginVisibility = 'private' | 'organization' | 'public'
type PluginSourceType = 'github' | 'url' | 'path'
interface Plugin {
id: string
// Basic info
name: string // kebab-case identifier
displayName: string
description: string
icon?: string
// Source
source: {
source: PluginSourceType
repo?: string // owner/repo (GitHub)
url?: string // Git URL
path?: string // Local path
ref?: string // branch/tag/commit
}
// Manifest (cached from repo)
manifest?: {
name: string
description: string
version: string
author?: {
name: string
email?: string
url?: string
}
homepage?: string
repository?: string
license?: string
keywords?: string[]
category?: string
// Component flags
commands?: boolean | string[]
agents?: boolean | string[]
skills?: boolean | string[]
hooks?: boolean | string[]
mcpServers?: boolean | string[]
}
// Ownership
organizationId: string
ownerId: string
visibility: PluginVisibility
// Stats
installCount?: number
starCount?: number
// Verification
verified?: boolean
featured?: boolean
// Timestamps
createdAt: Date
updatedAt: Date
// Soft delete
isDeleted?: boolean
deletedAt?: Date | null
}
Collection Associée: pluginInstallations/{installationId} suit les installations par espace.
Index Requis:
- Composite:
organizationId+visibility+createdAt(desc) visibilitypour la marketplacefeaturedpour les plugins en vedette
personal_access_tokens
Tokens API pour l’accès programmatique.
interface PersonalAccessToken {
id: string // Token ID (not the token itself)
uid: string // Owner user ID
organizationId: string
name: string // User-defined name
// Token hash (actual token never stored)
tokenHash: string
// Permissions
scopes: string[] // e.g., ["read:spaces", "write:chats"]
// Usage tracking
lastUsedAt?: Date | null
createdAt: Date
expiresAt?: Date | null
}
Sécurité: Géré côté serveur. Les utilisateurs peuvent uniquement lire/supprimer leurs propres tokens.
Index Requis:
- Composite:
uid+organizationId tokenHashpour l’authentification (unique)
invites
Gestion des invitations d’organisation.
interface Invite {
id: string
organizationId: string
email: string
role: 'member' | 'admin'
invitedBy: string // User ID
status: 'pending' | 'accepted' | 'expired'
expiresAt: Date
createdAt: Date
}
Sécurité: Lecture publique (nécessaire pour les liens d’invitation). Seuls les propriétaires d’organisation peuvent créer/mettre à jour/supprimer.
Collections Exclusivement Côté Serveur
Ces collections n’ont pas d’accès client et sont gérées exclusivement via le SDK Admin:
authStates
Mise en cache des niveaux d’authentification pour les performances.
cli_pending_auth
Codes OAuth temporaires pour l’authentification CLI.
cli_refresh_tokens
Tokens de rafraîchissement pour l’outil CLI.
partner_connections
Clés RSA pour l’authentification des partenaires (clés privées chiffrées).
partner_organizations
Lie les partenaires aux organisations.
usageLogs
Journaux d’utilisation détaillés par requête.
Collections en Lecture Seule (Client)
usageAggregates
Métriques d’utilisation agrégées. Structure: usageAggregates/{period}/data/{orgId}_{dateKey}
interface UsageAggregate {
organizationId: string
period: 'daily' | 'monthly'
dateKey: string // YYYY-MM-DD
// Metrics
totalRequests: number
totalTokensInput: number
totalTokensOutput: number
totalCost: number
// Breakdowns
byAgent: Record<string, {
requests: number
tokensInput: number
tokensOutput: number
cost: number
}>
}
Sécurité: Les membres de l’organisation peuvent lire. L’écriture est côté serveur uniquement.
creditTransactions
Historique du solde de crédits.
interface CreditTransaction {
id: string
organizationId: string
type: 'purchase' | 'usage' | 'refund' | 'grant'
amount: number // Positive or negative
balance: number // Running balance
description: string
metadata?: any
createdAt: Date
}
Sécurité: Les membres de l’organisation peuvent lire. L’écriture est côté serveur uniquement.
Résumé des Index
Index Composites Requis
// Organizations
organizations: members (array-contains)
// Characters
character: organizationId + isTemplate + createdAt (desc)
character: ownerId + visibility
// Spaces
spaces: organizationId + updatedAt (desc)
// Chats
chats: organizationId + spaceId + updatedAt (desc)
// Tasks
tasks: organizationId + status + createdAt (desc)
// Executions
executions: organizationId + chatId + startedAt (desc)
// Missions
missions: organizationId + status + createdAt (desc)
// Scheduled Tasks
scheduledTasks: organizationId + isActive + nextRun (asc)
// Skills
skills: organizationId + visibility + createdAt (desc)
skills: category + visibility
// Plugins
plugins: organizationId + visibility + createdAt (desc)
// Personal Access Tokens
personal_access_tokens: uid + organizationId
Index sur Champ Unique
La plupart des champs createdAt, updatedAt, ownerId, visibility sont automatiquement indexés par Firestore.
Résumé des Règles de Sécurité
Modèles d’Accès
Propriétaire uniquement:
users- Seul le propriétaire peut lire/mettre à jouruserProfiles- Seul le propriétaire peut lire/mettre à jourpersonal_access_tokens- Le propriétaire peut lire/supprimer
Accès Membres de l’Organisation:
organizations- Les membres peuvent lirespaces- Filtré par visibilitéchats- Propriétaire ou membres de l’organisationtasks- Membres de l’organisationexecutions- Membres de l’organisationmissions- Membres de l’organisationscheduledTasks- Membres de l’organisation
Basé sur la Visibilité:
character- Public/organisation/privé/non répertoriéskills- Public/organisation/privéplugins- Public/organisation/privé
Serveur Uniquement:
authStatescli_pending_authcli_refresh_tokenspartner_connectionspartner_organizationsusageLogs
Lecture Seule (Client):
usageAggregates- Les membres de l’organisation peuvent lirecreditTransactions- Les membres de l’organisation peuvent lire
Champs Chiffrés
Ces champs sont chiffrés côté serveur et ne peuvent pas être modifiés depuis le client:
organizations.encryptedAnthropicApiKeyuserProfiles.encryptedAnthropicApiKeyuserProfiles.encryptedClaudeCodeOAuthToken
Les mises à jour côté client tentant de modifier ces champs seront rejetées par les règles de sécurité.
Bonnes Pratiques
Contraintes Firestore
- Pas de Valeurs Undefined: Firestore n’autorise pas
undefined. Utiliseznullou omettez le champ.
// ❌ Mauvais
await updateDoc(docRef, { optional: undefined })
// ✅ Bon
await updateDoc(docRef, { optional: value ?? null })
// ✅ Meilleur - omettre undefined
const cleanData = Object.fromEntries(
Object.entries(data).filter(([_, v]) => v !== undefined)
)
- Utilisez les Server Timestamps: Utilisez toujours
serverTimestamp()pour la cohérence.
import { serverTimestamp } from 'firebase/firestore'
await addDoc(collection, {
...data,
createdAt: serverTimestamp()
})
- Nettoyez les Abonnements: Toujours se désabonner des écouteurs en temps réel.
const unsubscribe = onSnapshot(query, snapshot => { })
onUnmounted(() => {
if (unsubscribe) unsubscribe()
})
Suppressions Douces
La plupart des collections utilisent des suppressions douces pour la récupération:
// Soft delete
await updateDoc(docRef, {
isDeleted: true,
deletedAt: new Date()
})
// Restore
await updateDoc(docRef, {
isDeleted: false,
deletedAt: null
})
Mises à Jour Optimistes
Pour une meilleure UX, l’application utilise des mises à jour optimistes pour les créations:
// Add optimistic item immediately
optimisticItems.value.push(newItem)
// Create in Firestore
const docRef = await addDoc(collection, data)
// Snapshot listener updates with real data
Notes de Migration
Champs Hérités
Certaines collections ont des champs obsolètes maintenus pour la rétrocompatibilité:
Characters:
systemPrompt→ Utilisezsystem_messageskills(inline) → UtilisezskillIds(références)tools→ Utilisezadvanced_tools
UserProfiles:
anthropicApiKey(texte brut) → UtilisezencryptedAnthropicApiKeyclaudeCodeOAuthToken(texte brut) → UtilisezencryptedClaudeCodeOAuthToken
Migration de Données
Lors de la migration de données, toujours:
- Créer les nouveaux champs à côté des anciens
- Mettre à jour le code applicatif pour lire les deux
- Tâche de fond migre les données
- Supprimer les références aux anciens champs après migration complète
- Conserver les champs obsolètes documentés pendant 6 mois minimum