+2
.idea/dictionaries/project.xml
+2
.idea/dictionaries/project.xml
+7
-5
backend/config.example.toml
+7
-5
backend/config.example.toml
···
1
1
## This is a configuration file for Clippr.
2
2
## Please copy to "config.example.toml" before starting the server,
3
3
## otherwise it will not start. Modify as necessary.
4
-
## All keys that are listed are expected to be included in the config, even if not explicity declared as required.
5
4
6
5
## Where the server is broadcasted to.
7
6
hostname = "localhost"
8
7
port = 9090
9
-
webDomain = "https://localhost"
8
+
9
+
## Where the server will be deployed to.
10
+
baseUrl = "https://localhost:9090"
10
11
11
12
## For most deployments, you will want to keep the log level at "info".
12
13
## If you are a developer, move it down to "debug" for more information.
···
22
23
logLevel = "info"
23
24
24
25
## How the SQLite database is stored.
25
-
## For experimenting, you can store the database in memory with ":memory:"
26
+
## NOTE: Storing the database in-memory does not work as the schema is not properly loaded. Fixme!
26
27
[database]
27
28
## Paths can be used here.
28
29
name = "file:clippr.db"
···
30
31
## How the server interacts with the ATproto network.
31
32
[network]
32
33
## What Jetstream instance to use for receiving content from the network.
34
+
## Non-Bluesky relay: relay2.fire.hose.cam
33
35
firehose = "jetstream1.us-east.bsky.network"
34
36
## What DID to use for service proxying. This should be the domain that the API is accessible from.
35
37
## Default: "did:web:localhost%3A9090"
36
38
serviceDid = "did:web:localhost%3A9090"
37
-
## A multibase public key to use for signing in the service proxy DID, formatted as "did:key:[key]". Required.
38
-
## Do not use the default key outside of testing.
39
+
## A multibase public key to use for DID service proxying, formatted as "did:key:[key]".
40
+
## Do not use the default key outside of development.
39
41
didSigningKey = "did:key:zDnaeuuRRQuYp4S76LwosLhHbpU1HJcg6S5oJAUHmdZLVdLM5"
+2
-5
backend/drizzle.config.ts
+2
-5
backend/drizzle.config.ts
···
8
8
// @ts-expect-error Read from the TypeScript file instead of assuming that it's JavaScript
9
9
import { Config } from "./src/config.ts";
10
10
11
-
const config = Config.getInstance();
11
+
const config = Config.getInstance().getConfig();
12
12
let dbname;
13
13
14
-
dbname = config.get("database.name");
15
-
if (typeof dbname !== "string") {
16
-
dbname = "file:clippr.db"; // Only way to disable linter error
17
-
}
14
+
dbname = config.database.name;
18
15
19
16
export default defineConfig({
20
17
out: "./drizzle",
+35
-14
backend/src/config.ts
+35
-14
backend/src/config.ts
···
7
7
import { readFileSync } from "fs";
8
8
import * as toml from "toml";
9
9
10
+
interface ConfigSchema {
11
+
hostname: string | "localhost";
12
+
port: number | 9090;
13
+
baseUrl: string | "http://localhost:9090";
14
+
logLevel: string | "debug";
15
+
database: {
16
+
name: string | "file:clippr.db";
17
+
};
18
+
network: {
19
+
firehose: string | "jetstream1.us-east.bsky.network";
20
+
serviceDid: string | "did:web:localhost%3A9090";
21
+
didSigningKey: string | "did:key:zDnaeuuRRQuYp4S76LwosLhHbpU1HJcg6S5oJAUHmdZLVdLM5";
22
+
};
23
+
}
24
+
25
+
class ConfigError extends Error {
26
+
constructor(message: string) {
27
+
super(message);
28
+
this.name = "ConfigError";
29
+
}
30
+
}
31
+
10
32
export class Config {
11
33
private static instance: Config;
12
-
private readonly configData;
34
+
private readonly configData: ConfigSchema;
13
35
14
36
private constructor() {
15
37
let tomlString;
16
38
try {
17
39
tomlString = readFileSync("config.toml", "utf-8");
18
40
} catch {
19
-
throw new Error("Config file not found");
41
+
throw new ConfigError("Config file not found");
20
42
}
21
-
this.configData = toml.parse(tomlString);
43
+
44
+
try {
45
+
this.configData = toml.parse(tomlString);
46
+
} catch {
47
+
throw new ConfigError(`Config file is not valid TOML`);
48
+
}
22
49
}
23
50
51
+
/**
52
+
* Gets a singleton instance of the program configuration
53
+
* @throws {ConfigError} if the config file cannot be read or parsed
54
+
*/
24
55
static getInstance(): Config {
25
56
if (!Config.instance) {
26
57
try {
···
33
64
return Config.instance;
34
65
}
35
66
36
-
get<T>(path: string): T | undefined {
37
-
const keys = path.split(".");
38
-
let value = this.configData;
39
-
for (const key of keys) {
40
-
if (value == null) return undefined;
41
-
value = value[key];
42
-
}
43
-
return value;
44
-
}
45
-
46
-
getAll() {
67
+
getConfig(): ConfigSchema {
47
68
return this.configData;
48
69
}
49
70
}
+2
-2
backend/src/db/database.ts
+2
-2
backend/src/db/database.ts
···
8
8
import { Config } from "../config.js";
9
9
import Logger from "../logger.js";
10
10
11
-
const config = Config.getInstance();
12
-
const dbname = config.get("database.name");
11
+
const config = Config.getInstance().getConfig();
12
+
const dbname = config.database.name;
13
13
14
14
export class Database {
15
15
private static instance: Database;
+3
-2
backend/src/logger.ts
+3
-2
backend/src/logger.ts
···
5
5
*/
6
6
7
7
import { createLogger, format, transports } from "winston";
8
+
import { Config } from "./config.js";
8
9
9
-
// TODO: I can't seem to actually get the config setting for the log level yet.
10
-
const logLevel = "debug";
10
+
const config = Config.getInstance().getConfig();
11
+
const logLevel = config.logLevel;
11
12
12
13
const Logger = createLogger({
13
14
level: logLevel,
+4
-4
backend/src/main.ts
+4
-4
backend/src/main.ts
···
20
20
logger.info("Clippr-BE starting...");
21
21
22
22
logger.verbose("Reading configuration...");
23
-
const config = Config.getInstance();
23
+
const config = Config.getInstance().getConfig();
24
24
25
25
logger.verbose("Initializing database...");
26
26
Database.getInstance();
···
30
30
readFromFirehose();
31
31
32
32
const server: ServerType = serve({
33
-
port: config.get("port"),
34
-
hostname: config.get("hostname"),
33
+
port: config.port,
34
+
hostname: config.hostname,
35
35
fetch: app.fetch,
36
36
});
37
37
38
38
logger.info(
39
-
`XRPC server launched at http://${config.get("hostname")}:${config.get("port")}`,
39
+
`XRPC server launched at http://${config.hostname}:${config.port}`,
40
40
);
41
41
42
42
process.removeAllListeners("SIGINT");
+2
-2
backend/src/network/jetstream.ts
+2
-2
backend/src/network/jetstream.ts
···
9
9
import { handleClip, handleProfile, handleTag } from "./commit.js";
10
10
import Logger from "../logger.js";
11
11
12
-
const config = Config.getInstance();
13
-
const hostname = config.get("network.firehose");
12
+
const config = Config.getInstance().getConfig();
13
+
const hostname = config.network.firehose;
14
14
15
15
const jetstream = new Jetstream({
16
16
endpoint: `wss://${hostname}/subscribe`,
+10
-24
backend/src/routes/well-known.ts
+10
-24
backend/src/routes/well-known.ts
···
9
9
import { getStats } from "../api/stats.js";
10
10
11
11
const app = new Hono();
12
-
const config = Config.getInstance();
12
+
const config = Config.getInstance().getConfig();
13
13
14
-
const serviceDid: string =
15
-
config.get("network.serviceDid") ||
16
-
`did:web:${config.get("hostname")}%3A${config.get("port")}/`;
17
-
const signingKey: string | unknown = config.get("network.didSigningKey");
18
-
let webDomain: string =
19
-
config.get("network.webDomain") ||
20
-
`http://${config.get("hostname")}:${config.get("port")}`;
14
+
const serviceDid: string = config.network.serviceDid;
15
+
const signingKey: string = config.network.didSigningKey;
16
+
let baseUrl: string = config.baseUrl
21
17
22
-
if (!webDomain.startsWith("http://") || !webDomain.startsWith("https://")) {
23
-
webDomain = `http://${webDomain.replace(/^https?:\/\//, "")}`;
18
+
if (!baseUrl.startsWith("http://") || !baseUrl.startsWith("https://")) {
19
+
baseUrl = `http://${baseUrl.replace(/^https?:\/\//, "")}`;
24
20
}
25
21
26
22
app.get("/.well-known/nodeinfo", (c) => {
···
28
24
links: [
29
25
{
30
26
rel: "https://nodeinfo.diaspora.software/ns/schema/2.2",
31
-
href: `${webDomain}/nodeinfo/2.2`,
27
+
href: `${baseUrl}/nodeinfo/2.2`,
32
28
},
33
29
{
34
30
rel: "https://nodeinfo.diaspora.software/ns/schema/2.1",
35
-
href: `${webDomain}/nodeinfo/2.1`,
31
+
href: `${baseUrl}/nodeinfo/2.1`,
36
32
},
37
33
{
38
34
rel: "https://nodeinfo.diaspora.software/ns/schema/2.0",
39
-
href: `${webDomain}/nodeinfo/2.0`,
35
+
href: `${baseUrl}/nodeinfo/2.0`,
40
36
},
41
37
],
42
38
});
···
152
148
);
153
149
}
154
150
155
-
if (typeof signingKey !== "string") {
156
-
return c.json(
157
-
{
158
-
error: "Internal Server Error",
159
-
message: "Server is not properly configured",
160
-
},
161
-
500,
162
-
);
163
-
}
164
-
165
151
if (!signingKey.replace("did:key:", "").startsWith("z")) {
166
152
console.log(signingKey);
167
153
return c.json(
···
191
177
{
192
178
id: "#clippr_appview",
193
179
type: "ClipprAppView",
194
-
serviceEndpoint: `${webDomain}`,
180
+
serviceEndpoint: `${baseUrl}`,
195
181
},
196
182
],
197
183
});