temp commit

This commit is contained in:
Justin Visser 2026-02-26 12:54:19 +01:00
parent 8760f27b51
commit 0e9e49df16
193 changed files with 23228 additions and 935 deletions

View file

@ -41,7 +41,6 @@ export interface AdminDbRepairJob {
export interface AdminPdfQueueItem {
publication_id: number;
title: string;
doi: string | null;
display_identifier: DisplayIdentifier | null;
pdf_url: string | null;
status: string;
@ -160,3 +159,19 @@ export async function requeueAllAdminPdfLookups(limit = 1000): Promise<AdminPdfQ
);
return response.data;
}
export interface DropAllPublicationsResult {
deleted_count: number;
message: string;
}
export async function dropAllPublications(confirmationText: string): Promise<DropAllPublicationsResult> {
const response = await apiRequest<DropAllPublicationsResult>(
"/admin/db/drop-all-publications",
{
method: "POST",
body: { confirmation_text: confirmationText },
},
);
return response.data;
}

View file

@ -19,7 +19,6 @@ export interface PublicationItem {
citation_count: number;
venue_text: string | null;
pub_url: string | null;
doi: string | null;
display_identifier: DisplayIdentifier | null;
pdf_url: string | null;
pdf_status: "untracked" | "queued" | "running" | "resolved" | "failed";
@ -44,6 +43,7 @@ export interface PublicationsResult {
total_count: number;
page: number;
page_size: number;
snapshot: string;
has_next: boolean;
has_prev: boolean;
publications: PublicationItem[];
@ -53,8 +53,12 @@ export interface PublicationsQuery {
mode?: PublicationMode;
favoriteOnly?: boolean;
scholarProfileId?: number;
search?: string;
sortBy?: string;
sortDir?: "asc" | "desc";
page?: number;
pageSize?: number;
snapshot?: string;
}
export interface PublicationSelection {
@ -74,12 +78,24 @@ export async function listPublications(query: PublicationsQuery = {}): Promise<P
if (query.scholarProfileId) {
params.set("scholar_profile_id", String(query.scholarProfileId));
}
if (query.search && query.search.trim().length > 0) {
params.set("search", query.search.trim());
}
if (query.sortBy) {
params.set("sort_by", query.sortBy);
}
if (query.sortDir) {
params.set("sort_dir", query.sortDir);
}
const parsedPage = Number.isFinite(query.page) ? Math.max(1, Math.trunc(Number(query.page))) : 1;
const parsedPageSize = Number.isFinite(query.pageSize)
? Math.max(1, Math.min(500, Math.trunc(Number(query.pageSize))))
: 100;
params.set("page", String(parsedPage));
params.set("page_size", String(parsedPageSize));
if (query.snapshot && query.snapshot.trim().length > 0) {
params.set("snapshot", query.snapshot.trim());
}
const suffix = params.toString();
const response = await apiRequest<PublicationsResult>(

View file

@ -145,6 +145,13 @@ export async function triggerManualRun(): Promise<{
return response.data;
}
export async function cancelRun(runId: number): Promise<RunDetail> {
const response = await apiRequest<RunDetail>(`/runs/${runId}/cancel`, {
method: "POST",
});
return response.data;
}
export async function listQueueItems(limit = 200): Promise<QueueItem[]> {
const response = await apiRequest<QueueListData>(`/runs/queue/items?limit=${limit}`, {
method: "GET",

View file

@ -54,7 +54,6 @@ export interface PublicationExportItem {
author_text: string | null;
venue_text: string | null;
pub_url: string | null;
doi: string | null;
pdf_url: string | null;
is_read: boolean;
}
@ -86,7 +85,7 @@ interface ScholarsListData {
scholars: ScholarProfile[];
}
interface ScholarSearchData extends ScholarSearchResult {}
interface ScholarSearchData extends ScholarSearchResult { }
export async function listScholars(): Promise<ScholarProfile[]> {
const response = await apiRequest<ScholarsListData>("/scholars", { method: "GET" });

View file

@ -14,6 +14,7 @@ import AppRefreshButton from "@/components/ui/AppRefreshButton.vue";
import AppSelect from "@/components/ui/AppSelect.vue";
import AppTable from "@/components/ui/AppTable.vue";
import {
dropAllPublications,
getAdminDbIntegrityReport,
listAdminDbRepairJobs,
listAdminPdfQueue,
@ -42,6 +43,7 @@ const SECTION_PDF = "pdf";
const SCOPE_SINGLE_USER = "single_user";
const SCOPE_ALL_USERS = "all_users";
const APPLY_ALL_USERS_CONFIRM_TEXT = "REPAIR ALL USERS";
const DROP_PUBLICATIONS_CONFIRM_TEXT = "DROP ALL PUBLICATIONS";
type RepairScopeMode = typeof SCOPE_SINGLE_USER | typeof SCOPE_ALL_USERS;
@ -81,6 +83,13 @@ const repairDryRun = ref(true);
const repairGcOrphans = ref(false);
const repairConfirmationText = ref("");
const droppingPublications = ref(false);
const dropConfirmationText = ref("");
const dropResult = ref<{ deleted_count: number; message: string } | null>(null);
const dropConfirmationValid = computed(
() => dropConfirmationText.value.trim() === DROP_PUBLICATIONS_CONFIRM_TEXT,
);
const refreshingPdfQueue = ref(false);
const requeueingPublicationId = ref<number | null>(null);
const requeueingAllPdfs = ref(false);
@ -433,6 +442,22 @@ async function onRequeueAllPdfs(): Promise<void> {
}
}
async function onDropAllPublications(): Promise<void> {
droppingPublications.value = true;
clearAlerts();
dropResult.value = null;
try {
const result = await dropAllPublications(dropConfirmationText.value.trim());
dropResult.value = result;
successMessage.value = result.message;
dropConfirmationText.value = "";
} catch (error) {
assignError(error, "Unable to drop publications.");
} finally {
droppingPublications.value = false;
}
}
onMounted(async () => {
loading.value = true;
clearAlerts();
@ -686,6 +711,45 @@ watch(
</tbody>
</AppTable>
</AppCard>
<AppCard class="space-y-3 border-danger-300 dark:border-danger-700">
<div class="flex items-center gap-1">
<h2 class="text-lg font-semibold text-danger-600 dark:text-danger-400">Drop All Publications</h2>
<AppHelpHint text="Permanently delete ALL publications, links, identifiers, and PDF jobs. Scholar baselines will be reset so the next run re-discovers everything." />
</div>
<p class="text-sm text-secondary">
This action is <strong>irreversible</strong>. It deletes every publication record across all users.
The next ingestion run will re-populate all data from scratch.
</p>
<form class="grid gap-3" @submit.prevent="onDropAllPublications">
<label class="grid gap-1 text-sm font-medium text-ink-secondary">
<span>Type '{{ DROP_PUBLICATIONS_CONFIRM_TEXT }}' to confirm</span>
<AppInput
v-model="dropConfirmationText"
:placeholder="DROP_PUBLICATIONS_CONFIRM_TEXT"
autocomplete="off"
/>
</label>
<div>
<AppButton
type="submit"
variant="danger"
:disabled="droppingPublications || !dropConfirmationValid"
>
{{ droppingPublications ? "Dropping..." : "Drop all publications" }}
</AppButton>
</div>
</form>
<div v-if="dropResult" class="rounded-lg border border-stroke-default bg-surface-card-muted p-3 text-xs">
<div class="mb-1 flex items-center gap-2">
<AppBadge tone="danger">Deleted: {{ dropResult.deleted_count }}</AppBadge>
</div>
<p class="text-secondary">{{ dropResult.message }}</p>
</div>
</AppCard>
</section>
<AppCard v-if="props.section === SECTION_PDF" class="space-y-3">

View file

@ -19,6 +19,9 @@ export interface UserSettings {
nav_visible_pages: string[];
policy: UserSettingsPolicy;
safety_state: ScrapeSafetyState;
openalex_api_key: string | null;
crossref_api_token: string | null;
crossref_api_mailto: string | null;
}
export interface UserSettingsUpdate {
@ -26,6 +29,9 @@ export interface UserSettingsUpdate {
run_interval_minutes: number;
request_delay_seconds: number;
nav_visible_pages: string[];
openalex_api_key: string | null;
crossref_api_token: string | null;
crossref_api_mailto: string | null;
}
export interface ChangePasswordPayload {