mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07:05:24 +00:00
* feat: AI translations at release, 7 new languages and regional fallback * fix: proper plural forms for count strings and full translations * fix: plural forms for selected, hidden and active counts * fix: synced plugin updates failing on desktop and server-only plugins shown as stopped * fix: restore rounded corners on app icons and favicon * feat: desktop app auto updates with automatic, ask first or never modes * feat: community and custom plugin registries * feat: plugin guide for AI tools in the SDK with a lint check * fix: keep the terminal WebSocket open past nginx's 60s default (#1588) * chore: update changelog * feat: show plugin download counts only and stop CI builds inflating them * feat: hide installed plugins in the browse tab by default * chore: pin bundled plugins to latest releases * chore: update translations * fix: focusing a saved password field and saving no longer erases it * fix: tell desktop users when their 2.8 data needs 2.9 first * fix: point windows update info at the x64 installer * fix: keep desktop client tunnel sockets alive behind proxies * chore: pin bundled plugins to latest releases * fix: cover client tunnel keepalive in the c2s runtime tests * fix: typing after opening a host no longer goes to the host search * fix: accept tab and = separators in SSH config import (#1598) * chore: update changelog * chore: drop plugin versions from core changelog * chore: update translations * chore: lint, format, and bump version to 26.10.1 --------- Co-authored-by: Darnel Hunter <68697450+innotelinc@users.noreply.github.com> Co-authored-by: Iain <Iain1970@protonmail.com>
65 lines
1.9 KiB
TypeScript
65 lines
1.9 KiB
TypeScript
import { and, eq } from "drizzle-orm";
|
|
import { pluginInstallCounts } from "../db/schema.js";
|
|
import type { DatabaseContext } from "./database-context.js";
|
|
|
|
export type PluginInstallCountRecord = typeof pluginInstallCounts.$inferSelect;
|
|
|
|
export interface PluginInstallCountInput {
|
|
pluginId: string;
|
|
count: number;
|
|
}
|
|
|
|
/** Download counts for the store, one row per plugin and registry. */
|
|
export class PluginInstallCountRepository {
|
|
constructor(
|
|
private readonly context: DatabaseContext,
|
|
private readonly onWrite?: () => void | Promise<void>,
|
|
) {}
|
|
|
|
async listByRegistry(
|
|
registryId: string,
|
|
): Promise<PluginInstallCountRecord[]> {
|
|
return this.context.drizzle
|
|
.select()
|
|
.from(pluginInstallCounts)
|
|
.where(eq(pluginInstallCounts.registryId, registryId));
|
|
}
|
|
|
|
/** Writes only the rows that changed, so an unchanged sync costs no save. */
|
|
async upsertMany(
|
|
registryId: string,
|
|
entries: PluginInstallCountInput[],
|
|
now = new Date().toISOString(),
|
|
): Promise<number> {
|
|
const existing = new Map(
|
|
(await this.listByRegistry(registryId)).map((row) => [row.pluginId, row]),
|
|
);
|
|
let written = 0;
|
|
for (const entry of entries) {
|
|
const row = existing.get(entry.pluginId);
|
|
if (row && row.count === entry.count) continue;
|
|
if (row) {
|
|
await this.context.drizzle
|
|
.update(pluginInstallCounts)
|
|
.set({ count: entry.count, updatedAt: now })
|
|
.where(
|
|
and(
|
|
eq(pluginInstallCounts.pluginId, entry.pluginId),
|
|
eq(pluginInstallCounts.registryId, registryId),
|
|
),
|
|
);
|
|
} else {
|
|
await this.context.drizzle.insert(pluginInstallCounts).values({
|
|
pluginId: entry.pluginId,
|
|
registryId,
|
|
count: entry.count,
|
|
updatedAt: now,
|
|
});
|
|
}
|
|
written += 1;
|
|
}
|
|
if (written > 0) await this.onWrite?.();
|
|
return written;
|
|
}
|
|
}
|