feat: add author-username search to feed/gists/projects listings and partial-index migration

Extend `database.text_search_clause` with an `author_field` parameter that resolves username matches to user UIDs, enabling author-username search across the three public listings (`/feed`, `/gists`, `/projects`). Update the corresponding API docs and README route descriptions to reflect the new search scope. Add six partial indexes (`idx_comments_target_live`, `idx_votes_target_live`, `idx_reactions_target_live`, `idx_gists_live_created`, `idx_projects_live_created`, `idx_attachments_user_created_live`) to optimize filtered queries on non-deleted rows. Introduce a hot-settings cache (`_hot_settings`) with a 2-second TTL for `maintenance_mode`, `rate_limit_per_minute`, and `rate_limit_window_seconds`, plus a periodic rate-limit store sweep (`_sweep_rate_limit_store`) to evict stale IP entries every 60 seconds. Add `GZipMiddleware` to the FastAPI app for response compression.
This commit is contained in:
2026-06-19 22:24:51 +00:00
parent d10f1af118
commit e393722600
22 changed files with 269 additions and 52 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ class AiUsageMonitor {
});
}
this.subscribe();
this.poller = new Poller(() => this.poll(), 60000);
this.poller = new Poller(() => this.poll(), 60000, { pauseHidden: true });
}
subscribe() {
+1 -1
View File
@@ -25,7 +25,7 @@ class BackupMonitor {
this.bindScheduleForm();
this.bindActions();
this.subscribe();
this.poller = new Poller(() => this.poll(), this.pollMs);
this.poller = new Poller(() => this.poll(), this.pollMs, { pauseHidden: true });
}
subscribe() {
+2 -2
View File
@@ -105,7 +105,7 @@ export class ContainerInstance {
this.detailPoll = new Poller(async () => {
const detail = await Http.getJson(`${this.base}/instances/${this.uid}`);
this.applyDetail(detail);
}, 20000);
}, 20000, { pauseHidden: true });
}
applyDetail(detail) {
@@ -144,7 +144,7 @@ export class ContainerInstance {
pre.classList.add("ci-empty");
pre.textContent = "Logs are currently unavailable.";
}
}, 20000);
}, 20000, { pauseHidden: true });
}
renderLogs(logs) {
+4 -1
View File
@@ -15,7 +15,10 @@ export class ContainerList {
init() {
this.bindActions();
this.bindCreate();
this.poller = new Poller(() => this.refresh(), 20000, { immediate: false });
this.poller = new Poller(() => this.refresh(), 20000, {
immediate: false,
pauseHidden: true,
});
const pubsub = window.app && window.app.pubsub;
if (pubsub) {
pubsub.subscribe("container.list", (data) => this.render(data.instances || []));
+4 -1
View File
@@ -17,7 +17,10 @@ export class ContainerManager {
this.instances = data.instances || [];
this.bind();
this.renderInstances();
this.poller = new Poller(() => this.refresh(), 20000, { immediate: false });
this.poller = new Poller(() => this.refresh(), 20000, {
immediate: false,
pauseHidden: true,
});
const pubsub = window.app && window.app.pubsub;
if (pubsub) {
pubsub.subscribe(`project.${this.slug}.containers`, (payload) => {
+4 -3
View File
@@ -62,8 +62,9 @@ export class LocalTime {
}
refreshRelative() {
document
.querySelectorAll('[data-dt][data-dt-mode="ago"]')
.forEach((el) => this.apply(el));
if (document.hidden) return;
const relative = document.querySelectorAll('[data-dt][data-dt-mode="ago"]');
if (!relative.length) return;
relative.forEach((el) => this.apply(el));
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ class ServiceMonitor {
if (!container) return;
this.bindActions(container);
this.bindConfigForms(container);
this.poller = new Poller(() => this.poll(), 20000);
this.poller = new Poller(() => this.poll(), 20000, { pauseHidden: true });
const pubsub = window.app && window.app.pubsub;
if (pubsub) {
const topic = this.detail ? `admin.services.${this.detailName}` : "admin.services";