Files
aliasvault/apps/mobile-app/utils/dist/shared/vault-sql/index.js
2025-10-28 13:03:36 +01:00

1667 lines
65 KiB
JavaScript

// <auto-generated>
// This file was automatically generated. Do not edit manually.
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
COMPLETE_SCHEMA_SQL: () => COMPLETE_SCHEMA_SQL,
CreateVaultSqlGenerator: () => CreateVaultSqlGenerator,
MIGRATION_SCRIPTS: () => MIGRATION_SCRIPTS,
VAULT_VERSIONS: () => VAULT_VERSIONS,
VaultSqlGenerator: () => VaultSqlGenerator,
checkVersionCompatibility: () => checkVersionCompatibility,
extractVersionFromMigrationId: () => extractVersionFromMigrationId,
getLatestClientVersion: () => getLatestClientVersion
});
module.exports = __toCommonJS(index_exports);
// src/sql/SqlConstants.ts
var COMPLETE_SCHEMA_SQL = `
\uFEFFCREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
"MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
"ProductVersion" TEXT NOT NULL
);
BEGIN TRANSACTION;
CREATE TABLE "Aliases" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Aliases" PRIMARY KEY,
"Gender" VARCHAR NULL,
"FirstName" VARCHAR NULL,
"LastName" VARCHAR NULL,
"NickName" VARCHAR NULL,
"BirthDate" TEXT NOT NULL,
"AddressStreet" VARCHAR NULL,
"AddressCity" VARCHAR NULL,
"AddressState" VARCHAR NULL,
"AddressZipCode" VARCHAR NULL,
"AddressCountry" VARCHAR NULL,
"Hobbies" TEXT NULL,
"EmailPrefix" TEXT NULL,
"PhoneMobile" TEXT NULL,
"BankAccountIBAN" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
CREATE TABLE "Services" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Services" PRIMARY KEY,
"Name" TEXT NULL,
"Url" TEXT NULL,
"Logo" BLOB NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
CREATE TABLE "Credentials" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Credentials" PRIMARY KEY,
"AliasId" TEXT NOT NULL,
"Notes" TEXT NULL,
"Username" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"ServiceId" TEXT NOT NULL,
CONSTRAINT "FK_Credentials_Aliases_AliasId" FOREIGN KEY ("AliasId") REFERENCES "Aliases" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_Credentials_Services_ServiceId" FOREIGN KEY ("ServiceId") REFERENCES "Services" ("Id") ON DELETE CASCADE
);
CREATE TABLE "Attachment" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Attachment" PRIMARY KEY,
"Filename" TEXT NOT NULL,
"Blob" BLOB NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
CONSTRAINT "FK_Attachment_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE TABLE "Passwords" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Passwords" PRIMARY KEY,
"Value" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
CONSTRAINT "FK_Passwords_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE INDEX "IX_Attachment_CredentialId" ON "Attachment" ("CredentialId");
CREATE INDEX "IX_Credentials_AliasId" ON "Credentials" ("AliasId");
CREATE INDEX "IX_Credentials_ServiceId" ON "Credentials" ("ServiceId");
CREATE INDEX "IX_Passwords_CredentialId" ON "Passwords" ("CredentialId");
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240708094944_1.0.0-InitialMigration', '9.0.4');
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240708224522_1.0.1-EmptyTestMigration', '9.0.4');
ALTER TABLE "Aliases" RENAME COLUMN "EmailPrefix" TO "Email";
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240711204207_1.0.2-ChangeEmailColumn', '9.0.4');
CREATE TABLE "EncryptionKeys" (
"Id" TEXT NOT NULL CONSTRAINT "PK_EncryptionKeys" PRIMARY KEY,
"PublicKey" TEXT NOT NULL,
"PrivateKey" TEXT NOT NULL,
"IsPrimary" INTEGER NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240729105618_1.1.0-AddPkiTables', '9.0.4');
CREATE TABLE "Settings" (
"Key" TEXT NOT NULL CONSTRAINT "PK_Settings" PRIMARY KEY,
"Value" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240805073413_1.2.0-AddSettingsTable', '9.0.4');
CREATE TABLE "ef_temp_Aliases" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Aliases" PRIMARY KEY,
"BirthDate" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"Email" TEXT NULL,
"FirstName" VARCHAR NULL,
"Gender" VARCHAR NULL,
"LastName" VARCHAR NULL,
"NickName" VARCHAR NULL,
"UpdatedAt" TEXT NOT NULL
);
INSERT INTO "ef_temp_Aliases" ("Id", "BirthDate", "CreatedAt", "Email", "FirstName", "Gender", "LastName", "NickName", "UpdatedAt")
SELECT "Id", "BirthDate", "CreatedAt", "Email", "FirstName", "Gender", "LastName", "NickName", "UpdatedAt"
FROM "Aliases";
COMMIT;
PRAGMA foreign_keys = 0;
BEGIN TRANSACTION;
DROP TABLE "Aliases";
ALTER TABLE "ef_temp_Aliases" RENAME TO "Aliases";
COMMIT;
PRAGMA foreign_keys = 1;
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240805122422_1.3.0-UpdateIdentityStructure', '9.0.4');
BEGIN TRANSACTION;
CREATE TABLE "ef_temp_Credentials" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Credentials" PRIMARY KEY,
"AliasId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"Notes" TEXT NULL,
"ServiceId" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"Username" TEXT NULL,
CONSTRAINT "FK_Credentials_Aliases_AliasId" FOREIGN KEY ("AliasId") REFERENCES "Aliases" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_Credentials_Services_ServiceId" FOREIGN KEY ("ServiceId") REFERENCES "Services" ("Id") ON DELETE CASCADE
);
INSERT INTO "ef_temp_Credentials" ("Id", "AliasId", "CreatedAt", "Notes", "ServiceId", "UpdatedAt", "Username")
SELECT "Id", "AliasId", "CreatedAt", "Notes", "ServiceId", "UpdatedAt", "Username"
FROM "Credentials";
COMMIT;
PRAGMA foreign_keys = 0;
BEGIN TRANSACTION;
DROP TABLE "Credentials";
ALTER TABLE "ef_temp_Credentials" RENAME TO "Credentials";
COMMIT;
PRAGMA foreign_keys = 1;
BEGIN TRANSACTION;
CREATE INDEX "IX_Credentials_AliasId" ON "Credentials" ("AliasId");
CREATE INDEX "IX_Credentials_ServiceId" ON "Credentials" ("ServiceId");
COMMIT;
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240812141727_1.3.1-MakeUsernameOptional', '9.0.4');
BEGIN TRANSACTION;
ALTER TABLE "Settings" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Services" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Passwords" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "EncryptionKeys" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Credentials" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Attachment" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Aliases" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240916105320_1.4.0-AddSyncSupport', '9.0.4');
ALTER TABLE "Attachment" RENAME TO "Attachments";
CREATE TABLE "ef_temp_Attachments" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Attachments" PRIMARY KEY,
"Blob" BLOB NOT NULL,
"CreatedAt" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"Filename" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_Attachments_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
INSERT INTO "ef_temp_Attachments" ("Id", "Blob", "CreatedAt", "CredentialId", "Filename", "IsDeleted", "UpdatedAt")
SELECT "Id", "Blob", "CreatedAt", "CredentialId", "Filename", "IsDeleted", "UpdatedAt"
FROM "Attachments";
COMMIT;
PRAGMA foreign_keys = 0;
BEGIN TRANSACTION;
DROP TABLE "Attachments";
ALTER TABLE "ef_temp_Attachments" RENAME TO "Attachments";
COMMIT;
PRAGMA foreign_keys = 1;
BEGIN TRANSACTION;
CREATE INDEX "IX_Attachments_CredentialId" ON "Attachments" ("CredentialId");
COMMIT;
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240917191243_1.4.1-RenameAttachmentsPlural', '9.0.4');
BEGIN TRANSACTION;
CREATE TABLE "TotpCodes" (
"Id" TEXT NOT NULL CONSTRAINT "PK_TotpCodes" PRIMARY KEY,
"Name" TEXT NOT NULL,
"SecretKey" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL,
CONSTRAINT "FK_TotpCodes_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE INDEX "IX_TotpCodes_CredentialId" ON "TotpCodes" ("CredentialId");
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20250310131554_1.5.0-AddTotpCodes', '9.0.4');
PRAGMA foreign_keys = OFF;
-- Clean up any existing temp tables first
DROP TABLE IF EXISTS "__EFMigrationsHistory_temp";
DROP TABLE IF EXISTS "Aliases_temp";
DROP TABLE IF EXISTS "Services_temp";
DROP TABLE IF EXISTS "EncryptionKeys_temp";
DROP TABLE IF EXISTS "Settings_temp";
DROP TABLE IF EXISTS "Credentials_temp";
DROP TABLE IF EXISTS "Attachments_temp";
DROP TABLE IF EXISTS "Passwords_temp";
DROP TABLE IF EXISTS "TotpCodes_temp";
-- Create backup tables for all data
CREATE TABLE "__EFMigrationsHistory_temp" AS SELECT * FROM "__EFMigrationsHistory";
CREATE TABLE "Aliases_temp" AS SELECT * FROM "Aliases";
CREATE TABLE "Services_temp" AS SELECT * FROM "Services";
CREATE TABLE "EncryptionKeys_temp" AS SELECT * FROM "EncryptionKeys";
CREATE TABLE "Settings_temp" AS SELECT * FROM "Settings";
CREATE TABLE "Credentials_temp" AS SELECT * FROM "Credentials";
CREATE TABLE "Attachments_temp" AS SELECT * FROM "Attachments";
CREATE TABLE "Passwords_temp" AS SELECT * FROM "Passwords";
CREATE TABLE "TotpCodes_temp" AS SELECT * FROM "TotpCodes";
-- Delete orphaned records that do not have a valid FK to the credential object
DELETE FROM "Attachments_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
DELETE FROM "Passwords_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
DELETE FROM "TotpCodes_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
-- Delete orphaned credentials that do not have valid FKs to alias or service objects
DELETE FROM "Credentials_temp" WHERE "AliasId" NOT IN (SELECT "Id" FROM "Aliases_temp");
DELETE FROM "Credentials_temp" WHERE "ServiceId" NOT IN (SELECT "Id" FROM "Services_temp");
-- After cleaning credentials, clean dependent tables again in case we removed credentials
DELETE FROM "Attachments_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
DELETE FROM "Passwords_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
DELETE FROM "TotpCodes_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
-- Drop all existing tables
DROP TABLE "TotpCodes";
DROP TABLE "Passwords";
DROP TABLE "Attachments";
DROP TABLE "Credentials";
DROP TABLE "Settings";
DROP TABLE "EncryptionKeys";
DROP TABLE "Services";
DROP TABLE "Aliases";
DROP TABLE "__EFMigrationsHistory";
-- Recreate tables with proper constraints (no dependencies first)
CREATE TABLE "__EFMigrationsHistory" (
"MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
"ProductVersion" TEXT NOT NULL
);
CREATE TABLE "Aliases" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Aliases" PRIMARY KEY,
"BirthDate" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"Email" TEXT NULL,
"FirstName" VARCHAR NULL,
"Gender" VARCHAR NULL,
"LastName" VARCHAR NULL,
"NickName" VARCHAR NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE "Services" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Services" PRIMARY KEY,
"Name" TEXT NULL,
"Url" TEXT NULL,
"Logo" BLOB NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE "EncryptionKeys" (
"Id" TEXT NOT NULL CONSTRAINT "PK_EncryptionKeys" PRIMARY KEY,
"PublicKey" TEXT NOT NULL,
"PrivateKey" TEXT NOT NULL,
"IsPrimary" INTEGER NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE "Settings" (
"Key" TEXT NOT NULL CONSTRAINT "PK_Settings" PRIMARY KEY,
"Value" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0
);
-- Tables with foreign keys
CREATE TABLE "Credentials" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Credentials" PRIMARY KEY,
"AliasId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"Notes" TEXT NULL,
"ServiceId" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"Username" TEXT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "FK_Credentials_Aliases_AliasId" FOREIGN KEY ("AliasId") REFERENCES "Aliases" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_Credentials_Services_ServiceId" FOREIGN KEY ("ServiceId") REFERENCES "Services" ("Id") ON DELETE CASCADE
);
CREATE TABLE "Attachments" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Attachments" PRIMARY KEY,
"Blob" BLOB NOT NULL,
"CreatedAt" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"Filename" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_Attachments_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE TABLE "Passwords" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Passwords" PRIMARY KEY,
"Value" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "FK_Passwords_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE TABLE "TotpCodes" (
"Id" TEXT NOT NULL CONSTRAINT "PK_TotpCodes" PRIMARY KEY,
"Name" TEXT NOT NULL,
"SecretKey" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "FK_TotpCodes_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
-- Restore data from temp tables
INSERT INTO "__EFMigrationsHistory" SELECT * FROM "__EFMigrationsHistory_temp";
INSERT INTO "Aliases" SELECT * FROM "Aliases_temp";
INSERT INTO "Services" SELECT * FROM "Services_temp";
INSERT INTO "EncryptionKeys" SELECT * FROM "EncryptionKeys_temp";
INSERT INTO "Settings" SELECT * FROM "Settings_temp";
INSERT INTO "Credentials" SELECT * FROM "Credentials_temp";
INSERT INTO "Attachments" SELECT * FROM "Attachments_temp";
INSERT INTO "Passwords" SELECT * FROM "Passwords_temp";
INSERT INTO "TotpCodes" SELECT * FROM "TotpCodes_temp";
-- =====================================================================================
-- Date Format Normalization Migration
-- =====================================================================================
-- This migration normalizes ALL date fields to the standard format: 'yyyy-MM-dd HH:mm:ss.fff'
-- Previously the different clients used different date formats which complicate date parsing.
-- From version 0.24.0 onwards, all new dates are stored in this standard format.
-- Update Aliases table (CreatedAt, UpdatedAt, BirthDate)
UPDATE "Aliases" SET "CreatedAt" =
CASE
-- Already in correct format (yyyy-MM-dd HH:mm:ss.fff) - no change
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
-- ISO 8601 with milliseconds (yyyy-MM-ddTHH:mm:ss.fffZ) -> Replace T with space, remove Z and everything after .fff
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
-- Without milliseconds (yyyy-MM-dd HH:mm:ss or yyyy-MM-ddTHH:mm:ssZ) -> Add .000
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
-- Fallback: if none match, keep as-is (edge case)
ELSE "CreatedAt"
END;
UPDATE "Aliases" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- BirthDate: Always set time to 00:00:00 (no milliseconds for birth dates)
UPDATE "Aliases" SET "BirthDate" =
CASE
-- If empty or already '0001-01-01 00:00:00', keep as-is
WHEN "BirthDate" = '' OR "BirthDate" = '0001-01-01 00:00:00'
THEN "BirthDate"
-- If already in correct format (yyyy-MM-dd 00:00:00), keep as-is
WHEN "BirthDate" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] 00:00:00'
THEN "BirthDate"
-- Extract date part and set time to 00:00:00
WHEN "BirthDate" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]*'
THEN substr("BirthDate", 1, 10) || ' 00:00:00'
-- Fallback
ELSE "BirthDate"
END;
-- Update Services table (CreatedAt, UpdatedAt)
UPDATE "Services" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Services" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update EncryptionKeys table (CreatedAt, UpdatedAt)
UPDATE "EncryptionKeys" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "EncryptionKeys" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update Settings table (CreatedAt, UpdatedAt)
UPDATE "Settings" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Settings" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update Credentials table (CreatedAt, UpdatedAt)
UPDATE "Credentials" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Credentials" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update Attachments table (CreatedAt, UpdatedAt)
UPDATE "Attachments" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Attachments" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update Passwords table (CreatedAt, UpdatedAt)
UPDATE "Passwords" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Passwords" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update TotpCodes table (CreatedAt, UpdatedAt)
UPDATE "TotpCodes" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "TotpCodes" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- =====================================================================================
-- End of Date Format Normalization Migration
-- =====================================================================================
-- Recreate indexes
CREATE INDEX "IX_Credentials_AliasId" ON "Credentials" ("AliasId");
CREATE INDEX "IX_Credentials_ServiceId" ON "Credentials" ("ServiceId");
CREATE INDEX "IX_Attachments_CredentialId" ON "Attachments" ("CredentialId");
CREATE INDEX "IX_Passwords_CredentialId" ON "Passwords" ("CredentialId");
CREATE INDEX "IX_TotpCodes_CredentialId" ON "TotpCodes" ("CredentialId");
-- Clean up temp tables
DROP TABLE "__EFMigrationsHistory_temp";
DROP TABLE "Aliases_temp";
DROP TABLE "Services_temp";
DROP TABLE "EncryptionKeys_temp";
DROP TABLE "Settings_temp";
DROP TABLE "Credentials_temp";
DROP TABLE "Attachments_temp";
DROP TABLE "Passwords_temp";
DROP TABLE "TotpCodes_temp";
PRAGMA foreign_keys = ON;
CREATE TABLE "Passkeys" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Passkeys" PRIMARY KEY,
"RpId" TEXT COLLATE NOCASE NOT NULL,
"UserHandle" BLOB NOT NULL,
"PublicKey" TEXT NOT NULL,
"PrivateKey" TEXT NOT NULL,
"PrfKey" BLOB NULL,
"DisplayName" TEXT NOT NULL,
"AdditionalData" BLOB NULL,
"CredentialId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL,
CONSTRAINT "FK_Passkeys_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE INDEX "IX_Passkeys_CredentialId" ON "Passkeys" ("CredentialId");
CREATE INDEX "IX_Passkeys_RpId" ON "Passkeys" ("RpId");
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20251014122838_1.6.0-AddPasskeys', '9.0.4');
COMMIT;
`;
var MIGRATION_SCRIPTS = {
1: `\uFEFFBEGIN TRANSACTION;
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240708224522_1.0.1-EmptyTestMigration', '9.0.4');
COMMIT;`,
2: `\uFEFFBEGIN TRANSACTION;
ALTER TABLE "Aliases" RENAME COLUMN "EmailPrefix" TO "Email";
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240711204207_1.0.2-ChangeEmailColumn', '9.0.4');
COMMIT;`,
3: `\uFEFFBEGIN TRANSACTION;
CREATE TABLE "EncryptionKeys" (
"Id" TEXT NOT NULL CONSTRAINT "PK_EncryptionKeys" PRIMARY KEY,
"PublicKey" TEXT NOT NULL,
"PrivateKey" TEXT NOT NULL,
"IsPrimary" INTEGER NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240729105618_1.1.0-AddPkiTables', '9.0.4');
COMMIT;`,
4: `\uFEFFBEGIN TRANSACTION;
CREATE TABLE "Settings" (
"Key" TEXT NOT NULL CONSTRAINT "PK_Settings" PRIMARY KEY,
"Value" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240805073413_1.2.0-AddSettingsTable', '9.0.4');
COMMIT;`,
5: `\uFEFFBEGIN TRANSACTION;
CREATE TABLE "ef_temp_Aliases" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Aliases" PRIMARY KEY,
"BirthDate" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"Email" TEXT NULL,
"FirstName" VARCHAR NULL,
"Gender" VARCHAR NULL,
"LastName" VARCHAR NULL,
"NickName" VARCHAR NULL,
"UpdatedAt" TEXT NOT NULL
);
INSERT INTO "ef_temp_Aliases" ("Id", "BirthDate", "CreatedAt", "Email", "FirstName", "Gender", "LastName", "NickName", "UpdatedAt")
SELECT "Id", "BirthDate", "CreatedAt", "Email", "FirstName", "Gender", "LastName", "NickName", "UpdatedAt"
FROM "Aliases";
COMMIT;
PRAGMA foreign_keys = 0;
BEGIN TRANSACTION;
DROP TABLE "Aliases";
ALTER TABLE "ef_temp_Aliases" RENAME TO "Aliases";
COMMIT;
PRAGMA foreign_keys = 1;
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240805122422_1.3.0-UpdateIdentityStructure', '9.0.4');`,
6: `\uFEFFBEGIN TRANSACTION;
CREATE TABLE "ef_temp_Credentials" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Credentials" PRIMARY KEY,
"AliasId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"Notes" TEXT NULL,
"ServiceId" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"Username" TEXT NULL,
CONSTRAINT "FK_Credentials_Aliases_AliasId" FOREIGN KEY ("AliasId") REFERENCES "Aliases" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_Credentials_Services_ServiceId" FOREIGN KEY ("ServiceId") REFERENCES "Services" ("Id") ON DELETE CASCADE
);
INSERT INTO "ef_temp_Credentials" ("Id", "AliasId", "CreatedAt", "Notes", "ServiceId", "UpdatedAt", "Username")
SELECT "Id", "AliasId", "CreatedAt", "Notes", "ServiceId", "UpdatedAt", "Username"
FROM "Credentials";
COMMIT;
PRAGMA foreign_keys = 0;
BEGIN TRANSACTION;
DROP TABLE "Credentials";
ALTER TABLE "ef_temp_Credentials" RENAME TO "Credentials";
COMMIT;
PRAGMA foreign_keys = 1;
BEGIN TRANSACTION;
CREATE INDEX "IX_Credentials_AliasId" ON "Credentials" ("AliasId");
CREATE INDEX "IX_Credentials_ServiceId" ON "Credentials" ("ServiceId");
COMMIT;
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240812141727_1.3.1-MakeUsernameOptional', '9.0.4');`,
7: `\uFEFFBEGIN TRANSACTION;
ALTER TABLE "Settings" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Services" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Passwords" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "EncryptionKeys" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Credentials" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Attachment" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "Aliases" ADD "IsDeleted" INTEGER NOT NULL DEFAULT 0;
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240916105320_1.4.0-AddSyncSupport', '9.0.4');
COMMIT;`,
8: `\uFEFFBEGIN TRANSACTION;
ALTER TABLE "Attachment" RENAME TO "Attachments";
CREATE TABLE "ef_temp_Attachments" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Attachments" PRIMARY KEY,
"Blob" BLOB NOT NULL,
"CreatedAt" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"Filename" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_Attachments_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
INSERT INTO "ef_temp_Attachments" ("Id", "Blob", "CreatedAt", "CredentialId", "Filename", "IsDeleted", "UpdatedAt")
SELECT "Id", "Blob", "CreatedAt", "CredentialId", "Filename", "IsDeleted", "UpdatedAt"
FROM "Attachments";
COMMIT;
PRAGMA foreign_keys = 0;
BEGIN TRANSACTION;
DROP TABLE "Attachments";
ALTER TABLE "ef_temp_Attachments" RENAME TO "Attachments";
COMMIT;
PRAGMA foreign_keys = 1;
BEGIN TRANSACTION;
CREATE INDEX "IX_Attachments_CredentialId" ON "Attachments" ("CredentialId");
COMMIT;
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20240917191243_1.4.1-RenameAttachmentsPlural', '9.0.4');`,
9: `\uFEFFBEGIN TRANSACTION;
CREATE TABLE "TotpCodes" (
"Id" TEXT NOT NULL CONSTRAINT "PK_TotpCodes" PRIMARY KEY,
"Name" TEXT NOT NULL,
"SecretKey" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL,
CONSTRAINT "FK_TotpCodes_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE INDEX "IX_TotpCodes_CredentialId" ON "TotpCodes" ("CredentialId");
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20250310131554_1.5.0-AddTotpCodes', '9.0.4');
COMMIT;`,
10: `\uFEFFBEGIN TRANSACTION;
PRAGMA foreign_keys = OFF;
-- Clean up any existing temp tables first
DROP TABLE IF EXISTS "__EFMigrationsHistory_temp";
DROP TABLE IF EXISTS "Aliases_temp";
DROP TABLE IF EXISTS "Services_temp";
DROP TABLE IF EXISTS "EncryptionKeys_temp";
DROP TABLE IF EXISTS "Settings_temp";
DROP TABLE IF EXISTS "Credentials_temp";
DROP TABLE IF EXISTS "Attachments_temp";
DROP TABLE IF EXISTS "Passwords_temp";
DROP TABLE IF EXISTS "TotpCodes_temp";
-- Create backup tables for all data
CREATE TABLE "__EFMigrationsHistory_temp" AS SELECT * FROM "__EFMigrationsHistory";
CREATE TABLE "Aliases_temp" AS SELECT * FROM "Aliases";
CREATE TABLE "Services_temp" AS SELECT * FROM "Services";
CREATE TABLE "EncryptionKeys_temp" AS SELECT * FROM "EncryptionKeys";
CREATE TABLE "Settings_temp" AS SELECT * FROM "Settings";
CREATE TABLE "Credentials_temp" AS SELECT * FROM "Credentials";
CREATE TABLE "Attachments_temp" AS SELECT * FROM "Attachments";
CREATE TABLE "Passwords_temp" AS SELECT * FROM "Passwords";
CREATE TABLE "TotpCodes_temp" AS SELECT * FROM "TotpCodes";
-- Delete orphaned records that do not have a valid FK to the credential object
DELETE FROM "Attachments_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
DELETE FROM "Passwords_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
DELETE FROM "TotpCodes_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
-- Delete orphaned credentials that do not have valid FKs to alias or service objects
DELETE FROM "Credentials_temp" WHERE "AliasId" NOT IN (SELECT "Id" FROM "Aliases_temp");
DELETE FROM "Credentials_temp" WHERE "ServiceId" NOT IN (SELECT "Id" FROM "Services_temp");
-- After cleaning credentials, clean dependent tables again in case we removed credentials
DELETE FROM "Attachments_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
DELETE FROM "Passwords_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
DELETE FROM "TotpCodes_temp" WHERE "CredentialId" NOT IN (SELECT "Id" FROM "Credentials_temp");
-- Drop all existing tables
DROP TABLE "TotpCodes";
DROP TABLE "Passwords";
DROP TABLE "Attachments";
DROP TABLE "Credentials";
DROP TABLE "Settings";
DROP TABLE "EncryptionKeys";
DROP TABLE "Services";
DROP TABLE "Aliases";
DROP TABLE "__EFMigrationsHistory";
-- Recreate tables with proper constraints (no dependencies first)
CREATE TABLE "__EFMigrationsHistory" (
"MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
"ProductVersion" TEXT NOT NULL
);
CREATE TABLE "Aliases" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Aliases" PRIMARY KEY,
"BirthDate" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"Email" TEXT NULL,
"FirstName" VARCHAR NULL,
"Gender" VARCHAR NULL,
"LastName" VARCHAR NULL,
"NickName" VARCHAR NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE "Services" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Services" PRIMARY KEY,
"Name" TEXT NULL,
"Url" TEXT NULL,
"Logo" BLOB NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE "EncryptionKeys" (
"Id" TEXT NOT NULL CONSTRAINT "PK_EncryptionKeys" PRIMARY KEY,
"PublicKey" TEXT NOT NULL,
"PrivateKey" TEXT NOT NULL,
"IsPrimary" INTEGER NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE "Settings" (
"Key" TEXT NOT NULL CONSTRAINT "PK_Settings" PRIMARY KEY,
"Value" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0
);
-- Tables with foreign keys
CREATE TABLE "Credentials" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Credentials" PRIMARY KEY,
"AliasId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"Notes" TEXT NULL,
"ServiceId" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"Username" TEXT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "FK_Credentials_Aliases_AliasId" FOREIGN KEY ("AliasId") REFERENCES "Aliases" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_Credentials_Services_ServiceId" FOREIGN KEY ("ServiceId") REFERENCES "Services" ("Id") ON DELETE CASCADE
);
CREATE TABLE "Attachments" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Attachments" PRIMARY KEY,
"Blob" BLOB NOT NULL,
"CreatedAt" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"Filename" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_Attachments_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE TABLE "Passwords" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Passwords" PRIMARY KEY,
"Value" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "FK_Passwords_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE TABLE "TotpCodes" (
"Id" TEXT NOT NULL CONSTRAINT "PK_TotpCodes" PRIMARY KEY,
"Name" TEXT NOT NULL,
"SecretKey" TEXT NOT NULL,
"CredentialId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "FK_TotpCodes_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
-- Restore data from temp tables
INSERT INTO "__EFMigrationsHistory" SELECT * FROM "__EFMigrationsHistory_temp";
INSERT INTO "Aliases" SELECT * FROM "Aliases_temp";
INSERT INTO "Services" SELECT * FROM "Services_temp";
INSERT INTO "EncryptionKeys" SELECT * FROM "EncryptionKeys_temp";
INSERT INTO "Settings" SELECT * FROM "Settings_temp";
INSERT INTO "Credentials" SELECT * FROM "Credentials_temp";
INSERT INTO "Attachments" SELECT * FROM "Attachments_temp";
INSERT INTO "Passwords" SELECT * FROM "Passwords_temp";
INSERT INTO "TotpCodes" SELECT * FROM "TotpCodes_temp";
-- =====================================================================================
-- Date Format Normalization Migration
-- =====================================================================================
-- This migration normalizes ALL date fields to the standard format: 'yyyy-MM-dd HH:mm:ss.fff'
-- Previously the different clients used different date formats which complicate date parsing.
-- From version 0.24.0 onwards, all new dates are stored in this standard format.
-- Update Aliases table (CreatedAt, UpdatedAt, BirthDate)
UPDATE "Aliases" SET "CreatedAt" =
CASE
-- Already in correct format (yyyy-MM-dd HH:mm:ss.fff) - no change
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
-- ISO 8601 with milliseconds (yyyy-MM-ddTHH:mm:ss.fffZ) -> Replace T with space, remove Z and everything after .fff
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
-- Without milliseconds (yyyy-MM-dd HH:mm:ss or yyyy-MM-ddTHH:mm:ssZ) -> Add .000
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
-- Fallback: if none match, keep as-is (edge case)
ELSE "CreatedAt"
END;
UPDATE "Aliases" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- BirthDate: Always set time to 00:00:00 (no milliseconds for birth dates)
UPDATE "Aliases" SET "BirthDate" =
CASE
-- If empty or already '0001-01-01 00:00:00', keep as-is
WHEN "BirthDate" = '' OR "BirthDate" = '0001-01-01 00:00:00'
THEN "BirthDate"
-- If already in correct format (yyyy-MM-dd 00:00:00), keep as-is
WHEN "BirthDate" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] 00:00:00'
THEN "BirthDate"
-- Extract date part and set time to 00:00:00
WHEN "BirthDate" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]*'
THEN substr("BirthDate", 1, 10) || ' 00:00:00'
-- Fallback
ELSE "BirthDate"
END;
-- Update Services table (CreatedAt, UpdatedAt)
UPDATE "Services" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Services" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update EncryptionKeys table (CreatedAt, UpdatedAt)
UPDATE "EncryptionKeys" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "EncryptionKeys" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update Settings table (CreatedAt, UpdatedAt)
UPDATE "Settings" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Settings" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update Credentials table (CreatedAt, UpdatedAt)
UPDATE "Credentials" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Credentials" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update Attachments table (CreatedAt, UpdatedAt)
UPDATE "Attachments" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Attachments" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update Passwords table (CreatedAt, UpdatedAt)
UPDATE "Passwords" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "Passwords" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- Update TotpCodes table (CreatedAt, UpdatedAt)
UPDATE "TotpCodes" SET "CreatedAt" =
CASE
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "CreatedAt"
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr("CreatedAt", 12, 12)
WHEN "CreatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("CreatedAt", 1, 10) || ' ' || substr(replace("CreatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "CreatedAt"
END;
UPDATE "TotpCodes" SET "UpdatedAt" =
CASE
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]'
THEN "UpdatedAt"
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr("UpdatedAt", 12, 12)
WHEN "UpdatedAt" GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][T ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]*'
THEN substr("UpdatedAt", 1, 10) || ' ' || substr(replace("UpdatedAt", 'T', ' '), 12, 8) || '.000'
ELSE "UpdatedAt"
END;
-- =====================================================================================
-- End of Date Format Normalization Migration
-- =====================================================================================
-- Recreate indexes
CREATE INDEX "IX_Credentials_AliasId" ON "Credentials" ("AliasId");
CREATE INDEX "IX_Credentials_ServiceId" ON "Credentials" ("ServiceId");
CREATE INDEX "IX_Attachments_CredentialId" ON "Attachments" ("CredentialId");
CREATE INDEX "IX_Passwords_CredentialId" ON "Passwords" ("CredentialId");
CREATE INDEX "IX_TotpCodes_CredentialId" ON "TotpCodes" ("CredentialId");
-- Clean up temp tables
DROP TABLE "__EFMigrationsHistory_temp";
DROP TABLE "Aliases_temp";
DROP TABLE "Services_temp";
DROP TABLE "EncryptionKeys_temp";
DROP TABLE "Settings_temp";
DROP TABLE "Credentials_temp";
DROP TABLE "Attachments_temp";
DROP TABLE "Passwords_temp";
DROP TABLE "TotpCodes_temp";
PRAGMA foreign_keys = ON;
CREATE TABLE "Passkeys" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Passkeys" PRIMARY KEY,
"RpId" TEXT COLLATE NOCASE NOT NULL,
"UserHandle" BLOB NOT NULL,
"PublicKey" TEXT NOT NULL,
"PrivateKey" TEXT NOT NULL,
"PrfKey" BLOB NULL,
"DisplayName" TEXT NOT NULL,
"AdditionalData" BLOB NULL,
"CredentialId" TEXT NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"IsDeleted" INTEGER NOT NULL,
CONSTRAINT "FK_Passkeys_Credentials_CredentialId" FOREIGN KEY ("CredentialId") REFERENCES "Credentials" ("Id") ON DELETE CASCADE
);
CREATE INDEX "IX_Passkeys_CredentialId" ON "Passkeys" ("CredentialId");
CREATE INDEX "IX_Passkeys_RpId" ON "Passkeys" ("RpId");
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20251014122838_1.6.0-AddPasskeys', '9.0.4');
COMMIT;`
};
// src/sql/VaultVersions.ts
var VAULT_VERSIONS = [
{
revision: 1,
version: "1.0.0",
description: "Initial Migration",
releaseVersion: "0.1.0",
compatibleUpToVersion: "0.0.0"
},
{
revision: 2,
version: "1.0.1",
description: "Empty Test Migration",
releaseVersion: "0.2.0",
compatibleUpToVersion: "0.1.0"
},
{
revision: 3,
version: "1.0.2",
description: "Change Email Column",
releaseVersion: "0.3.0",
compatibleUpToVersion: "0.2.0"
},
{
revision: 4,
version: "1.1.0",
description: "Add Pki Tables",
releaseVersion: "0.4.0",
compatibleUpToVersion: "0.3.0"
},
{
revision: 5,
version: "1.2.0",
description: "Add Settings Table",
releaseVersion: "0.4.0",
compatibleUpToVersion: "0.3.0"
},
{
revision: 6,
version: "1.3.0",
description: "Update Identity Structure",
releaseVersion: "0.5.0",
compatibleUpToVersion: "0.4.0"
},
{
revision: 7,
version: "1.3.1",
description: "Make Username Optional",
releaseVersion: "0.5.0",
compatibleUpToVersion: "0.4.0"
},
{
revision: 8,
version: "1.4.0",
description: "Add Sync Support",
releaseVersion: "0.6.0",
compatibleUpToVersion: "0.5.0"
},
{
revision: 9,
version: "1.4.1",
description: "Rename Attachments Plural",
releaseVersion: "0.6.0",
compatibleUpToVersion: "0.13.0"
},
{
revision: 10,
version: "1.5.0",
description: "Add 2FA Tokens to credentials",
releaseVersion: "0.14.0",
compatibleUpToVersion: "0.23.0"
},
{
revision: 11,
version: "1.6.0",
description: "Add Passkey support",
releaseVersion: "0.24.0",
compatibleUpToVersion: "0.24.0"
}
];
// src/sql/VaultSqlGenerator.ts
var VaultSqlGenerator = class {
/**
* Get SQL commands to create a new vault with the latest schema
*/
getCreateVaultSql() {
try {
const sqlCommands = [
COMPLETE_SCHEMA_SQL
];
return {
success: true,
sqlCommands,
version: VAULT_VERSIONS[VAULT_VERSIONS.length - 1].version,
migrationNumber: VAULT_VERSIONS[VAULT_VERSIONS.length - 1].revision
};
} catch (error) {
return {
success: false,
sqlCommands: [],
version: "0.0.0",
migrationNumber: 0,
error: error instanceof Error ? error.message : "Unknown error creating vault SQL"
};
}
}
/**
* Get SQL commands to upgrade vault from current version to target version
*/
getUpgradeVaultSql(currentMigrationNumber, targetMigrationNumber) {
try {
const targetMigration = targetMigrationNumber ?? VAULT_VERSIONS[VAULT_VERSIONS.length - 1].revision;
const targetVersionInfo = VAULT_VERSIONS.find((v) => v.revision === targetMigration);
if (!targetVersionInfo) {
return {
success: false,
sqlCommands: [],
version: "0.0.0",
migrationNumber: 0,
error: `Target migration number ${targetMigration} not found`
};
}
if (currentMigrationNumber >= targetMigration) {
return {
success: true,
sqlCommands: [],
version: targetVersionInfo.version,
migrationNumber: targetMigration
};
}
const migrationsToApply = VAULT_VERSIONS.filter(
(v) => v.revision > currentMigrationNumber && v.revision <= targetMigration
);
const sqlCommands = [];
for (const migration of migrationsToApply) {
const migrationKey = migration.revision - 1;
const migrationSql = MIGRATION_SCRIPTS[migrationKey];
if (migrationSql) {
sqlCommands.push(migrationSql);
}
}
return {
success: true,
sqlCommands,
version: targetVersionInfo.version,
migrationNumber: targetMigration
};
} catch (error) {
return {
success: false,
sqlCommands: [],
version: "0.0.0",
migrationNumber: 0,
error: error instanceof Error ? error.message : "Unknown error generating upgrade SQL"
};
}
}
/**
* Get SQL commands to upgrade vault to latest version
*/
getUpgradeToLatestSql(currentMigrationNumber) {
return this.getUpgradeVaultSql(currentMigrationNumber);
}
/**
* Get SQL commands to upgrade vault to a specific version
*/
getUpgradeToVersionSql(currentMigrationNumber, targetVersion) {
const targetVersionInfo = VAULT_VERSIONS.find((v) => v.version === targetVersion);
if (!targetVersionInfo) {
return {
success: false,
sqlCommands: [],
version: "0.0.0",
migrationNumber: 0,
error: `Target version ${targetVersion} not found`
};
}
return this.getUpgradeVaultSql(currentMigrationNumber, targetVersionInfo.revision);
}
/**
* Get SQL commands to check current vault version
*/
getVersionCheckSql() {
return [
// Check if Settings table exists
"SELECT name FROM sqlite_master WHERE type='table' AND name='Settings';",
// Get vault version
"SELECT Value FROM Settings WHERE Key = 'vault_version' AND IsDeleted = 0 LIMIT 1;",
// Get migration number
"SELECT Value FROM Settings WHERE Key = 'vault_migration_number' AND IsDeleted = 0 LIMIT 1;"
];
}
/**
* Get SQL command to validate vault structure
*/
getVaultValidationSql() {
return `SELECT name FROM sqlite_master WHERE type='table' AND name IN
('Aliases', 'Services', 'Credentials', 'Passwords', 'Attachments', 'EncryptionKeys', 'Settings', 'TotpCodes');`;
}
/**
* Parse vault version information from query results
*/
parseVaultVersionInfo(settingsTableExists, versionResult, migrationResult) {
let currentVersion = "0.0.0";
let currentMigrationNumber = 0;
if (settingsTableExists) {
if (versionResult) {
currentVersion = versionResult;
} else {
currentVersion = "1.0.0";
currentMigrationNumber = 1;
}
if (migrationResult) {
currentMigrationNumber = parseInt(migrationResult, 10);
}
}
const latestVersion = VAULT_VERSIONS[VAULT_VERSIONS.length - 1];
const needsUpgrade = currentMigrationNumber < latestVersion.revision;
const availableUpgrades = VAULT_VERSIONS.filter((v) => v.revision > currentMigrationNumber);
return {
currentVersion,
currentMigrationNumber,
targetVersion: latestVersion.version,
targetMigrationNumber: latestVersion.revision,
needsUpgrade,
availableUpgrades
};
}
/**
* Validate vault structure from table names
*/
validateVaultStructure(tableNames) {
const requiredTables = ["Aliases", "Services", "Credentials", "Passwords", "Attachments", "EncryptionKeys", "Settings", "TotpCodes"];
const foundTables = tableNames.filter((name) => requiredTables.includes(name));
return foundTables.length >= 5;
}
/**
* Get all available vault versions
*/
getAllVersions() {
return [...VAULT_VERSIONS];
}
/**
* Get current/latest vault version info
*/
getLatestVersion() {
return VAULT_VERSIONS[VAULT_VERSIONS.length - 1];
}
/**
* Get specific migration SQL by migration number
*/
getMigrationSql(migrationNumber) {
return MIGRATION_SCRIPTS[migrationNumber];
}
/**
* Get complete schema SQL for creating new vault
*/
getCompleteSchemaSql() {
return COMPLETE_SCHEMA_SQL;
}
};
// src/factories/VaultSqlGeneratorFactory.ts
var CreateVaultSqlGenerator = () => {
return new VaultSqlGenerator();
};
// src/utils/VersionCompatibility.ts
function parseSemanticVersion(version) {
const versionRegex = /^(\d+)\.(\d+)\.(\d+)$/;
const match = versionRegex.exec(version);
if (!match) {
return null;
}
return {
major: parseInt(match[1], 10),
minor: parseInt(match[2], 10),
patch: parseInt(match[3], 10)
};
}
function checkVersionCompatibility(databaseVersion, clientVersionToCompare) {
const dbVersion = parseSemanticVersion(databaseVersion);
if (!dbVersion) {
return {
isCompatible: false,
databaseVersion,
isKnownVersion: false,
isMajorVersionDifference: false,
isMinorVersionDifference: false
};
}
const knownVersion = VAULT_VERSIONS.find((v) => v.version === databaseVersion);
if (knownVersion) {
return {
isCompatible: true,
databaseVersion,
clientVersion: knownVersion,
isKnownVersion: true,
isMajorVersionDifference: false,
isMinorVersionDifference: false
};
}
const latestClientVersion = clientVersionToCompare ? VAULT_VERSIONS.find((v) => v.version === clientVersionToCompare) ?? getLatestClientVersion() : getLatestClientVersion();
const clientVersion = parseSemanticVersion(latestClientVersion.version);
if (!clientVersion) {
return {
isCompatible: false,
databaseVersion,
isKnownVersion: false,
isMajorVersionDifference: false,
isMinorVersionDifference: false
};
}
const isMajorVersionDifference = dbVersion.major !== clientVersion.major;
const isMinorVersionDifference = !isMajorVersionDifference && (dbVersion.minor !== clientVersion.minor || dbVersion.patch !== clientVersion.patch);
const isCompatible = !isMajorVersionDifference;
return {
isCompatible,
databaseVersion,
clientVersion: latestClientVersion,
isKnownVersion: false,
isMajorVersionDifference,
isMinorVersionDifference
};
}
function getLatestClientVersion() {
return VAULT_VERSIONS[VAULT_VERSIONS.length - 1];
}
function extractVersionFromMigrationId(migrationId) {
const versionRegex = /_(\d+\.\d+\.\d+)-/;
const versionMatch = versionRegex.exec(migrationId);
return versionMatch?.[1] ?? null;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
COMPLETE_SCHEMA_SQL,
CreateVaultSqlGenerator,
MIGRATION_SCRIPTS,
VAULT_VERSIONS,
VaultSqlGenerator,
checkVersionCompatibility,
extractVersionFromMigrationId,
getLatestClientVersion
});