···11## This is a configuration file for Clippr.
22## Please copy to "config.example.toml" before starting the server,
33## otherwise it will not start. Modify as necessary.
44+## All keys that are listed are expected to be included in the config, even if not explicity declared as required.
4566+## Where the server is broadcasted to.
57hostname = "localhost"
68port = 9090
99+webDomain = "https://localhost"
710811## For most deployments, you will want to keep the log level at "info".
99-## If you are debugging the software, move it down to "debug".
1212+## If you are a developer, move it down to "debug" for more information.
1013##
1114## List of all recognized log levels, sorted by importance:
1215## "error" - Critical errors, typically leading to a crash or a degraded state.
···1619## "verbose" - Details more of the server's operations.
1720## "debug" - Information for developers, meant for debugging.
1821## "silly" - Anything goes.
1919-log-level = "info"
2222+logLevel = "info"
20232124## How the SQLite database is stored.
2222-## For testing, you can store the database in memory with ":memory:"
2525+## For experimenting, you can store the database in memory with ":memory:"
2326[database]
2427## Paths can be used here.
2528name = "file:clippr.db"
26292730## How the server interacts with the ATproto network.
2831[network]
2929-firehose = "jetstream1.us-east.bsky.network"3232+## What Jetstream instance to use for receiving content from the network.
3333+firehose = "jetstream1.us-east.bsky.network"
3434+## What DID to use for service proxying. This should be the domain that the API is accessible from.
3535+## Default: "did:web:localhost%3A9090"
3636+serviceDid = "did:web:localhost%3A9090"
3737+## A multibase public key to use for signing in the service proxy DID, formatted as "did:key:[key]". Required.
3838+## Do not use the default key outside of testing.
3939+didSigningKey = "did:key:zDnaeuuRRQuYp4S76LwosLhHbpU1HJcg6S5oJAUHmdZLVdLM5"
+36
backend/src/api/stats.ts
···11+/*
22+ * clippr: a social bookmarking service for the AT Protocol
33+ * Copyright (c) 2025 clippr contributors.
44+ * SPDX-License-Identifier: AGPL-3.0-only
55+ */
66+77+import type { AppviewStatsQuery } from "./types.js";
88+import { Database } from "../db/database.js";
99+import { clipsTable, tagsTable, usersTable } from "../db/schema.js";
1010+import { count } from "drizzle-orm";
1111+1212+const db = Database.getInstance().getDb();
1313+1414+export async function getStats(): Promise<AppviewStatsQuery> {
1515+ const clipCount = await db.select({ count: count() }).from(clipsTable);
1616+ const tagCount = await db.select({ count: count() }).from(tagsTable);
1717+ const userCount = await db.select({ count: count() }).from(usersTable);
1818+1919+ if (
2020+ clipCount[0] === undefined ||
2121+ tagCount[0] === undefined ||
2222+ userCount[0] === undefined
2323+ ) {
2424+ return {
2525+ knownClips: 0,
2626+ knownTags: 0,
2727+ knownUsers: 0,
2828+ };
2929+ }
3030+3131+ return {
3232+ knownClips: clipCount[0].count,
3333+ knownTags: tagCount[0].count,
3434+ knownUsers: userCount[0].count,
3535+ };
3636+}
···6677import { Hono } from "hono";
88import misc from "./routes/misc.js";
99+import openapi from "./routes/openapi.js";
910import xrpc from "./routes/xrpc.js";
1111+import wellKnown from "./routes/well-known.js";
1012import Logger from "./logger.js";
1113import { logger } from "hono/logger";
1212-import openapi from "./routes/openapi.js";
1314import { cors } from "hono/cors";
14151516export function winstonLogger(message: string, ...rest: unknown[]) {
···2324// Link all routes up
2425app.route("/", misc);
2526app.route("/", openapi);
2727+app.route("/", wellKnown);
2628app.route("/xrpc", xrpc);
27292830export default app;