Skip to content

Database Schema ​

Teldock stores metadata in MySQL/MariaDB through Sequelize. The models live in backend/src/models/ and are registered together in backend/src/models/index.js, which also declares the associations. Raw file bytes are never stored in the database.

Entity relationships ​

User ​

Account record and owner of all other resources. Table: users.

ColumnTypeNotes
idUUIDPrimary key, default UUIDV4.
telegramIdBIGINTUnique, nullable.
emailSTRING(255)Unique.
usernameSTRING(255)Nullable.
firstName / lastNameSTRING(100)Nullable.
avatarUrlSTRING(500)Nullable.
passwordHashSTRING(255)Nullable; bcrypt hash.
storageQuotaBytesBIGINTNullable; NULL means no fixed quota.
storageUsedBytesBIGINTDefault 0; approximate usage for stats.
premiumUntilDATENullable.
isPremiumBOOLEANDefault false.
isActiveBOOLEANDefault true.

Indexes: telegramId, email. Helper methods: canUpload(), User.updateStorageUsage().

BotToken ​

A pool of Telegram bot tokens per user for parallel throughput. Table: bot_tokens.

ColumnTypeNotes
idUUIDPrimary key.
userIdUUIDFK to users.id, ON DELETE CASCADE.
tokenEncryptedTEXTAES-256-CBC encrypted bot token (iv:ciphertext).
botUsernameSTRING(100)Display name.
botIdBIGINTNumeric Telegram bot ID.
isActiveBOOLEANWhether the bot is available in the pool.
lastUsedAtDATETimestamp for round-robin fairness.

Indexes: userId, isActive. getToken() decrypts the stored value on demand.

File ​

File metadata and Telegram references. Table: files.

ColumnTypeNotes
idUUIDPrimary key.
userIdUUIDFK to users.id, ON DELETE CASCADE.
folderIdUUIDFK to folders.id, ON DELETE SET NULL.
telegramChatIdBIGINTStorage channel ID (not exposed by the API).
telegramMessageIdINTEGERNullable for chunked files.
telegramFileIdSTRING(255)Nullable for chunked files.
originalFilenameSTRING(500)Name at upload time.
displayFilenameSTRING(500)Name shown in the UI.
mimeTypeSTRING(100)MIME type.
fileSizeBIGINTSize in bytes.
isChunkedBOOLEANSplit across multiple Telegram messages.
partCountINTEGERDefault 1.
isEncryptedBOOLEANWhether parts are encrypted at rest.
encryptionSaltSTRING(64)Hex salt for the per-file key.
checksumSTRING(64)SHA-256 of the original file.
isPublicBOOLEANDefault false.
isFavoriteBOOLEANDefault false.
sharedTokenSTRING(64)Unique, nullable.
downloadCountINTEGERDefault 0.
lastDownloadedAtDATENullable.
isDeletedBOOLEANSoft-delete flag.
deletedAtDATENullable.

Indexes: userId, folderId, idx_telegram_file (telegramFileId), idx_user_files (userId, isDeleted, createdAt), idx_telegram_chat_msg (telegramChatId, telegramMessageId), unique idx_shared_token (sharedToken), and a FULLTEXT idx_search (originalFilename, mimeType).

INFO

File.toJSON() strips telegramChatId, telegramMessageId, and telegramFileId so internal storage identifiers never reach API responses.

FilePart ​

One chunk of a large file, referencing a single Telegram message. Table: file_parts.

ColumnTypeNotes
idUUIDPrimary key.
fileIdUUIDFK to files.id, ON DELETE CASCADE.
partIndexINTEGERZero-based order within the file.
telegramChatIdBIGINTChannel holding this part.
telegramMessageIdINTEGERMessage ID of this part.
telegramFileIdSTRING(255)Telegram file ID of this part.
partSizeBIGINTStored size (encrypted size if encrypted).
plainSizeBIGINTPre-encryption size.
encryptionIvSTRING(32)Hex IV, nullable.
checksumSTRING(64)SHA-256 of the plaintext part.

Indexes: fileId, and unique idx_file_part_order (fileId, partIndex).

FileVersion ​

Immutable snapshot of a file for version history. Table: file_versions.

ColumnTypeNotes
idUUIDPrimary key.
fileIdUUIDFK to files.id, ON DELETE CASCADE.
userIdUUIDFK to users.id, ON DELETE CASCADE.
versionNumberINTEGERSequential (1, 2, 3...).
telegramMessageId / telegramFileId / telegramChatIdmixedSingle-part convenience references.
isChunkedBOOLEANDefault false.
partCountINTEGERDefault 1.
isEncryptedBOOLEANDefault false.
encryptionSaltSTRING(64)Nullable.
partsSnapshotLONGTEXTJSON array of the version's FilePart records.
originalFilename / displayFilenameSTRING(500)Names at snapshot time.
mimeTypeSTRING(100)MIME type.
fileSizeBIGINTSize in bytes.
checksumSTRING(64)SHA-256 hash.

Indexes: fileId, userId, versionNumber. getMetadata() returns a trimmed summary.

FileTag ​

Join table for the many-to-many File ↔ Tag relationship. Table: file_tags.

ColumnTypeNotes
fileIdUUIDFK to files.id, ON DELETE CASCADE.
tagIdUUIDFK to tags.id, ON DELETE CASCADE.

Indexes: unique uniq_file_tag (fileId, tagId). No timestamps.

Folder ​

Hierarchical folders owned by a user. Table: folders.

ColumnTypeNotes
idUUIDPrimary key.
userIdUUIDFK to users.id, ON DELETE CASCADE.
parentFolderIdUUIDSelf-reference, ON DELETE SET NULL.
nameSTRING(255)Required.
pathSTRING(1000)Full path (for example /Documents/Projects).
depthINTEGERRoot is 0.
displayOrderINTEGERSort order within the parent.
iconSTRING(50)Default "folder".

Indexes: userId, parentFolderId, folders_tree_index (userId, parentFolderId, displayOrder), and unique folders_path_idx (path).

A public share created for a file. Table: shared_links.

ColumnTypeNotes
idUUIDPrimary key.
fileIdUUIDFK to files.id, ON DELETE CASCADE.
creatorIdUUIDFK to users.id, ON DELETE CASCADE.
tokenSTRING(512)Unique JWT used in the share URL.
passwordHashSTRING(255)Optional bcrypt hash.
downloadLimitINTEGERNullable (unlimited).
usedDownloadsINTEGERDefault 0.
expiresAtDATENullable (never expires).
allowDownloadBOOLEANDefault true.
allowPreviewBOOLEANDefault true.
accessedCountINTEGERDefault 0.
lastAccessedAtDATENullable.

Indexes: token, idx_expires (expiresAt), idx_file_creator (fileId, creatorId). SharedLink.consumeDownload() reserves a download with one conditional update so concurrent requests cannot exceed the limit.

SmartFolder ​

A saved file filter (not a real folder). Table: smart_folders.

ColumnTypeNotes
idUUIDPrimary key.
userIdUUIDFK to users.id, ON DELETE CASCADE.
nameSTRING(80)Required.
iconSTRING(30)Default "sparkles".
criteriaTEXTJSON: { favorite?, tagId?, type?, q? }.

Indexes: userId.

Tag ​

A user-defined label. Table: tags.

ColumnTypeNotes
idUUIDPrimary key.
userIdUUIDFK to users.id, ON DELETE CASCADE.
nameSTRING(50)Required.
colorSTRING(20)Default "#10b981".

Indexes: userId, and unique uniq_user_tag (userId, name).

TelegramConfig ​

Per-user Telegram bot and storage channel credentials. Table: user_telegram_configs.

ColumnTypeNotes
idUUIDPrimary key.
userIdUUIDFK to users.id, ON DELETE CASCADE.
botTokenEncryptedTEXTAES-256-CBC encrypted bot token.
storageChatIdSTRING(50)Storage chat/channel ID.
chatTypeENUM('channel','group','private')Default "channel".
usernameSTRING(100)Nullable.
isActiveTINYINT(1)Default 1.

Indexes: userId, isActive. TelegramConfig.toJSON() strips botTokenEncrypted and storageChatId; decryptToken() decrypts on demand.

Chunked storage model ​

Telegram's Bot API limits a single upload, so Teldock splits larger files into bounded parts (about 18 MB, controlled by TG_PART_SIZE). Each part is uploaded as its own Telegram message and recorded as a FilePart row:

  • File.isChunked marks a multi-part file, and File.partCount records how many parts exist.
  • Each FilePart stores the partIndex, the Telegram telegramChatId, telegramMessageId, and telegramFileId for that chunk, plus its size and optional encryption IV.
  • On download, parts are read back in partIndex order, decrypted when needed, and stitched into a single stream with HTTP Range support.
  • FileVersion.partsSnapshot preserves the full part list for chunked/encrypted files so a prior version can be restored exactly.

Migrations ​

Migration scripts live in backend/scripts/ and are run from the backend directory.

Scriptnpm commandPurpose
migrate.jsnpm run migrateCreate/update all tables from the models.
migrate-chunked-storage.jsnpm run migrate:chunkedAdd chunked/encrypted columns to files.
migrate-drop-quota.jsnpm run migrate:drop-quotaMake storageQuotaBytes nullable and clear the legacy quota.
migrate-widen-share-token.jsnpm run migrate:widen-share-tokenWiden shared_links.token to VARCHAR(512).
migrate-version-snapshots.jsnpm run migrate:version-snapshotsAdd part-snapshot columns to file_versions.
migrate-tags-favorites.jsnpm run migrate:tags-favoritesAdd files.isFavorite, tags, file_tags, smart_folders.

Two additional scripts are run directly with Node when upgrading older databases: scripts/migrate-file-versions.js and scripts/migrate-telegram-configs.js.

bash
cd backend
npm run migrate

See Deployment for running migrations in production.

Released under the MIT License.