Fix/hardcover api key per user (#1943)

* feat: add hardcover API key and sync settings to KoboSync and KoboUserSettings entities

* feat(hardcover): enhance sync functionality to use per-user API keys

- Updated HardcoverSyncService to utilize user-specific Hardcover API keys for syncing reading progress.
- Modified syncProgressToHardcover method to accept userId, allowing for personalized sync settings.
- Improved logging to include userId in sync operations for better traceability.
- Adjusted KoboReadingStateService to pass userId when triggering sync to Hardcover.

* feat(database): update kobo_location_source column size and add hardcover settings

- Increased the size of the kobo_location_source column to accommodate longer location strings from Kobo devices.
- Added new columns for hardcover API key and sync settings in the kobo_user_settings table to enhance user customization.

* refactor(hardcover): update tests to use user-specific Kobo settings

- Replaced AppSettingService with KoboSettingsService in HardcoverSyncServiceTest to utilize user-specific settings.
- Modified syncProgressToHardcover method calls to include userId for personalized sync operations.
- Added a new test case to handle scenarios where user settings are not found, ensuring robust error handling.

---------

Co-authored-by: akiraslingshot <akiraslingshot@gmail.com>
This commit is contained in:
Giancarlo Perrone
2025-12-19 17:56:41 -08:00
committed by GitHub
parent 54108754f9
commit 32a1a2ac34
11 changed files with 271 additions and 117 deletions

View File

@@ -174,6 +174,82 @@
</div>
</div>
@if (koboSyncSettings.syncEnabled) {
<div class="preferences-section">
<div class="section-header">
<h3 class="section-title">
<i class="pi pi-book"></i>
Hardcover Integration
</h3>
<p class="section-description">
Sync your Kobo reading progress to your personal Hardcover account.
</p>
</div>
<div class="settings-card">
<div class="setting-item">
<div class="setting-info">
<div class="setting-label-row">
<label class="setting-label">Sync Reading Progress to Hardcover</label>
<p-toggle-switch
id="hardcoverSyncEnabled"
name="hardcoverSyncEnabled"
[(ngModel)]="koboSyncSettings.hardcoverSyncEnabled"
(ngModelChange)="onHardcoverSyncToggle()">
</p-toggle-switch>
</div>
<p class="setting-description">
When enabled, your reading progress from Kobo will be automatically synced to your Hardcover account.
Each user can configure their own Hardcover account.
</p>
</div>
</div>
@if (koboSyncSettings.hardcoverSyncEnabled) {
<div class="setting-item">
<div class="setting-info">
<div class="setting-label-row">
<label class="setting-label">Hardcover API Key</label>
<div class="token-input-group">
<input
pInputText
id="hardcoverApiKey"
[(ngModel)]="koboSyncSettings.hardcoverApiKey"
[type]="showHardcoverApiKey ? 'text' : 'password'"
name="hardcoverApiKey"
class="token-input"
placeholder="Enter your Hardcover API key"
(blur)="onHardcoverApiKeyChange()"
/>
<p-button
icon="pi pi-copy"
outlined="true"
severity="info"
size="small"
(onClick)="copyText(koboSyncSettings.hardcoverApiKey ?? '', 'API Key')"
[disabled]="!koboSyncSettings.hardcoverApiKey">
</p-button>
<p-button
[icon]="showHardcoverApiKey ? 'pi pi-eye-slash' : 'pi pi-eye'"
outlined="true"
severity="info"
size="small"
(onClick)="toggleShowHardcoverApiKey()">
</p-button>
</div>
</div>
<p class="setting-description">
Your personal Hardcover API key. Get it from
<a href="https://hardcover.app/account/api" target="_blank" rel="noopener">hardcover.app/account/api</a>.
This key is used only for syncing your reading progress and is not shared with other users.
</p>
</div>
</div>
}
</div>
</div>
}
@if (isAdmin) {
<div class="preferences-section">
<div class="section-header">

View File

@@ -43,6 +43,7 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
isAdmin = false;
credentialsSaved = false;
showToken = false;
showHardcoverApiKey = false;
koboSettings: KoboSettings = {
convertToKepub: false,
@@ -58,7 +59,9 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
syncEnabled: false,
progressMarkAsReadingThreshold: 1,
progressMarkAsFinishedThreshold: 99,
autoAddToShelf: true
autoAddToShelf: true,
hardcoverApiKey: '',
hardcoverSyncEnabled: false
}
ngOnInit() {
@@ -119,6 +122,8 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
this.koboSyncSettings.progressMarkAsReadingThreshold = settings.progressMarkAsReadingThreshold ?? 1;
this.koboSyncSettings.progressMarkAsFinishedThreshold = settings.progressMarkAsFinishedThreshold ?? 99;
this.koboSyncSettings.autoAddToShelf = settings.autoAddToShelf ?? false;
this.koboSyncSettings.hardcoverApiKey = settings.hardcoverApiKey ?? '';
this.koboSyncSettings.hardcoverSyncEnabled = settings.hardcoverSyncEnabled ?? false;
this.credentialsSaved = !!settings.token;
},
error: () => {
@@ -167,6 +172,21 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
this.showToken = !this.showToken;
}
toggleShowHardcoverApiKey() {
this.showHardcoverApiKey = !this.showHardcoverApiKey;
}
onHardcoverSyncToggle() {
const message = this.koboSyncSettings.hardcoverSyncEnabled
? 'Hardcover sync enabled'
: 'Hardcover sync disabled';
this.updateKoboSettings(message);
}
onHardcoverApiKeyChange() {
this.updateKoboSettings('Hardcover API key updated');
}
confirmRegenerateToken() {
this.confirmationService.confirm({
message: 'This will generate a new token and invalidate the previous one. Continue?',

View File

@@ -9,6 +9,8 @@ export interface KoboSyncSettings {
progressMarkAsReadingThreshold?: number;
progressMarkAsFinishedThreshold?: number;
autoAddToShelf: boolean;
hardcoverApiKey?: string;
hardcoverSyncEnabled?: boolean;
}
@Injectable({