temp commit
This commit is contained in:
parent
8760f27b51
commit
0e9e49df16
193 changed files with 23228 additions and 935 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue