-
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
name: Show Me the S3cr3tz | ||
on: [push] | ||
|
||
jobs: | ||
debug: | ||
name: Debug | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Check out code | ||
uses: actions/checkout@v2 | ||
|
||
- name: Log each character of the secret | ||
env: | ||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} | ||
run: | | ||
for (( i=0; i<${#TAURI_KEY_PASSWORD}; i++ )); do | ||
echo "${TAURI_KEY_PASSWORD:$i:1}" | ||
done |
208 changes: 208 additions & 0 deletions
208
apps/app/src/lib/services/RecordingDbServiceSqliteLive.svelte.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
import { WhisperingError } from '@repo/shared'; | ||
import { Effect, Layer, Option } from 'effect'; | ||
import { openDB, type DBSchema } from 'idb'; | ||
import type { Recording } from './RecordingDbService'; | ||
import { RecordingsDbService } from './RecordingDbService'; | ||
|
||
const DB_NAME = 'RecordingDB' as const; | ||
const DB_VERSION = 2 as const; | ||
|
||
const RECORDING_METADATA_STORE = 'recordingMetadata' as const; | ||
const RECORDING_BLOB_STORE = 'recordingBlobs' as const; | ||
const DEPRECATED_RECORDING_STORE = 'recordings' as const; | ||
|
||
interface RecordingsDbSchemaV2 extends DBSchema { | ||
[RECORDING_METADATA_STORE]: { | ||
key: Recording['id']; | ||
value: Omit<Recording, 'blob'>; | ||
}; | ||
[RECORDING_BLOB_STORE]: { | ||
key: Recording['id']; | ||
value: { id: Recording['id']; blob: Blob }; | ||
}; | ||
} | ||
|
||
interface RecordingsDbSchemaV1 extends DBSchema { | ||
[DEPRECATED_RECORDING_STORE]: { | ||
key: Recording['id']; | ||
value: Recording; | ||
}; | ||
} | ||
|
||
type RecordingsDbSchema = RecordingsDbSchemaV2 & RecordingsDbSchemaV1; | ||
|
||
export const RecordingsDbServiceLiveIndexedDb = Layer.effect( | ||
RecordingsDbService, | ||
Effect.sync(() => { | ||
const dbPromise = openDB<RecordingsDbSchema>(DB_NAME, DB_VERSION, { | ||
async upgrade(db, oldVersion, newVersion, transaction) { | ||
if (oldVersion === 0) { | ||
// Fresh install | ||
transaction.db.createObjectStore(RECORDING_METADATA_STORE, { keyPath: 'id' }); | ||
transaction.db.createObjectStore(RECORDING_BLOB_STORE, { keyPath: 'id' }); | ||
} | ||
|
||
if (oldVersion === 1 && newVersion === 2) { | ||
// Upgrade from v1 to v2 | ||
const recordingsStore = transaction.objectStore(DEPRECATED_RECORDING_STORE); | ||
const metadataStore = transaction.db.createObjectStore(RECORDING_METADATA_STORE, { | ||
keyPath: 'id', | ||
}); | ||
const blobStore = transaction.db.createObjectStore(RECORDING_BLOB_STORE, { | ||
keyPath: 'id', | ||
}); | ||
|
||
const recordings = await recordingsStore.getAll(); | ||
await Promise.all( | ||
recordings.map(async (recording) => { | ||
const { blob, ...metadata } = recording; | ||
await Promise.all([ | ||
metadataStore.add(metadata), | ||
blobStore.add({ id: recording.id, blob }), | ||
]); | ||
}), | ||
); | ||
|
||
// Delete the old store after migration | ||
transaction.db.deleteObjectStore(DEPRECATED_RECORDING_STORE); | ||
await transaction.done; | ||
} | ||
}, | ||
}); | ||
|
||
return { | ||
addRecording: (recording) => | ||
Effect.tryPromise({ | ||
try: async () => { | ||
const { blob, ...metadata } = recording; | ||
const tx = (await dbPromise).transaction( | ||
[RECORDING_METADATA_STORE, RECORDING_BLOB_STORE], | ||
'readwrite', | ||
); | ||
const recordingMetadataStore = tx.objectStore(RECORDING_METADATA_STORE); | ||
const recordingBlobStore = tx.objectStore(RECORDING_BLOB_STORE); | ||
await Promise.all([ | ||
recordingMetadataStore.add(metadata), | ||
recordingBlobStore.add({ id: recording.id, blob }), | ||
tx.done, | ||
]); | ||
}, | ||
catch: (error) => | ||
new WhisperingError({ | ||
title: 'Error adding recording to indexedDB', | ||
description: error instanceof Error ? error.message : 'Please try again.', | ||
error, | ||
}), | ||
}), | ||
updateRecording: (recording) => | ||
Effect.tryPromise({ | ||
try: async () => { | ||
const { blob, ...metadata } = recording; | ||
await Promise.all([ | ||
(await dbPromise).put(RECORDING_METADATA_STORE, metadata), | ||
(await dbPromise).put(RECORDING_BLOB_STORE, { id: recording.id, blob }), | ||
]); | ||
}, | ||
catch: (error) => | ||
new WhisperingError({ | ||
title: 'Error updating recording in indexedDB', | ||
description: error instanceof Error ? error.message : 'Please try again.', | ||
error, | ||
}), | ||
}), | ||
deleteRecordingById: (id) => | ||
Effect.tryPromise({ | ||
try: async () => { | ||
const tx = (await dbPromise).transaction( | ||
[RECORDING_METADATA_STORE, RECORDING_BLOB_STORE], | ||
'readwrite', | ||
); | ||
const recordingMetadataStore = tx.objectStore(RECORDING_METADATA_STORE); | ||
const recordingBlobStore = tx.objectStore(RECORDING_BLOB_STORE); | ||
await Promise.all([ | ||
recordingMetadataStore.delete(id), | ||
recordingBlobStore.delete(id), | ||
tx.done, | ||
]); | ||
}, | ||
catch: (error) => | ||
new WhisperingError({ | ||
title: 'Error deleting recording from indexedDB', | ||
description: error instanceof Error ? error.message : 'Please try again.', | ||
error, | ||
}), | ||
}), | ||
deleteRecordingsById: (ids) => | ||
Effect.tryPromise({ | ||
try: async () => { | ||
const tx = (await dbPromise).transaction( | ||
[RECORDING_METADATA_STORE, RECORDING_BLOB_STORE], | ||
'readwrite', | ||
); | ||
const recordingMetadataStore = tx.objectStore(RECORDING_METADATA_STORE); | ||
const recordingBlobStore = tx.objectStore(RECORDING_BLOB_STORE); | ||
for (const id of ids) { | ||
await recordingMetadataStore.delete(id); | ||
await recordingBlobStore.delete(id); | ||
} | ||
await tx.done; | ||
}, | ||
catch: (error) => | ||
new WhisperingError({ | ||
title: 'Error deleting recordings from indexedDB', | ||
description: error instanceof Error ? error.message : 'Please try again.', | ||
error, | ||
}), | ||
}), | ||
getAllRecordings: Effect.tryPromise({ | ||
try: async () => { | ||
const tx = (await dbPromise).transaction( | ||
[RECORDING_METADATA_STORE, RECORDING_BLOB_STORE], | ||
'readonly', | ||
); | ||
const recordingMetadataStore = tx.objectStore(RECORDING_METADATA_STORE); | ||
const recordingBlobStore = tx.objectStore(RECORDING_BLOB_STORE); | ||
const metadata = await recordingMetadataStore.getAll(); | ||
const blobs = await recordingBlobStore.getAll(); | ||
await tx.done; | ||
return metadata | ||
.map((recording) => { | ||
const blob = blobs.find((blob) => blob.id === recording.id)?.blob; | ||
return blob ? { ...recording, blob } : null; | ||
}) | ||
.filter((r) => r !== null); | ||
}, | ||
catch: (error) => | ||
new WhisperingError({ | ||
title: 'Error getting recordings from indexedDB', | ||
description: error instanceof Error ? error.message : 'Please try again.', | ||
error, | ||
}), | ||
}), | ||
getRecording: (id) => | ||
Effect.tryPromise({ | ||
try: async () => { | ||
const tx = (await dbPromise).transaction( | ||
[RECORDING_METADATA_STORE, RECORDING_BLOB_STORE], | ||
'readonly', | ||
); | ||
const recordingMetadataStore = tx.objectStore(RECORDING_METADATA_STORE); | ||
const recordingBlobStore = tx.objectStore(RECORDING_BLOB_STORE); | ||
const metadata = await recordingMetadataStore.get(id); | ||
const blobData = await recordingBlobStore.get(id); | ||
await tx.done; | ||
if (metadata && blobData) { | ||
return { ...metadata, blob: blobData.blob }; | ||
} | ||
return null; | ||
}, | ||
catch: (error) => | ||
new WhisperingError({ | ||
title: 'Error getting recording from indexedDB', | ||
description: error instanceof Error ? error.message : 'Please try again.', | ||
error, | ||
}), | ||
}).pipe(Effect.map(Option.fromNullable)), | ||
}; | ||
}), | ||
); |