+65
-2
flake.nix
+65
-2
flake.nix
···
12
12
nixpkgsFor = forAllSystems (system:
13
13
import nixpkgs {
14
14
inherit system;
15
-
overlays = [self.overlay.default];
15
+
overlays = [self.overlays.default];
16
16
});
17
17
in {
18
-
overlay.default = final: prev: let
18
+
overlays.default = final: prev: let
19
19
pname = "plonk";
20
20
version = "0.1.0";
21
21
in {
···
23
23
buildNpmPackage {
24
24
inherit pname version;
25
25
src = ./.;
26
+
nativeBuildInputs = [makeBinaryWrapper];
26
27
packageJson = ./package.json;
27
28
buildPhase = "npm run build";
29
+
installPhase = ''
30
+
runHook preInstall
31
+
32
+
mkdir -p $out/bin
33
+
cp -r ./* $out/
34
+
35
+
makeBinaryWrapper ${nodejs}/bin/node $out/bin/$pname \
36
+
--prefix PATH : ${lib.makeBinPath [nodejs]} \
37
+
--add-flags "$out/dist/index.js"
38
+
'';
39
+
28
40
npmDepsHash = "sha256-qGCbaFAHd/s9hOTWMjHCam6Kf6pU6IWPybfwYh0sOwc=";
29
41
};
30
42
};
···
48
60
});
49
61
50
62
formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra);
63
+
64
+
nixosModules.default = {
65
+
config,
66
+
pkgs,
67
+
lib,
68
+
...
69
+
}:
70
+
with lib; {
71
+
options = {
72
+
services.plonk = {
73
+
enable = mkOption {
74
+
type = types.bool;
75
+
default = false;
76
+
description = "Enable plonk";
77
+
};
78
+
port = mkOption {
79
+
type = types.int;
80
+
default = 3000;
81
+
description = "Port to run plonk on";
82
+
};
83
+
cookie_secret = mkOption {
84
+
type = types.str;
85
+
default = "00000000000000000000000000000000";
86
+
description = "Cookie secret";
87
+
};
88
+
};
89
+
};
90
+
91
+
config = mkIf config.services.plonk.enable {
92
+
nixpkgs.overlays = [self.overlays.default];
93
+
systemd.services.plonk = {
94
+
description = "plonk service";
95
+
wantedBy = ["multi-user.target"];
96
+
97
+
serviceConfig = {
98
+
ListenStream = "0.0.0.0:${toString config.services.plonk.port}";
99
+
ExecStart = "${pkgs.plonk}/bin/plonk";
100
+
Restart = "always";
101
+
};
102
+
103
+
environment = {
104
+
PLONK_PORT = "${toString config.services.plonk.port}";
105
+
PLONK_NODE_ENV = "production";
106
+
PLONK_HOST = "localhost";
107
+
PLONK_PUBLIC_URL = "https://plonk.li";
108
+
PLONK_DB_PATH = "plonk.db";
109
+
PLONK_COOKIE_SECRET = config.services.plonk.cookie_secret;
110
+
};
111
+
};
112
+
};
113
+
};
51
114
};
52
115
}
+35
lexicons/comment.json
+35
lexicons/comment.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "li.plonk.comment",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"key": "tid",
8
+
"record": {
9
+
"type": "object",
10
+
"required": [
11
+
"content",
12
+
"createdAt",
13
+
"post"
14
+
],
15
+
"properties": {
16
+
"content": {
17
+
"type": "string",
18
+
"maxLength": 100000,
19
+
"maxGraphemes": 10000,
20
+
"description": "comment body"
21
+
},
22
+
"createdAt": {
23
+
"type": "string",
24
+
"format": "datetime",
25
+
"description": "comment creation timestamp"
26
+
},
27
+
"post": {
28
+
"type": "ref",
29
+
"ref": "com.atproto.repo.strongRef"
30
+
}
31
+
}
32
+
}
33
+
}
34
+
}
35
+
}
+67
lexicons/getRecord.json
+67
lexicons/getRecord.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "com.atproto.repo.getRecord",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "Get a single record from a repository. Does not require auth.",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": [
11
+
"repo",
12
+
"collection",
13
+
"rkey"
14
+
],
15
+
"properties": {
16
+
"repo": {
17
+
"type": "string",
18
+
"format": "at-identifier",
19
+
"description": "The handle or DID of the repo."
20
+
},
21
+
"collection": {
22
+
"type": "string",
23
+
"format": "nsid",
24
+
"description": "The NSID of the record collection."
25
+
},
26
+
"rkey": {
27
+
"type": "string",
28
+
"description": "The Record Key."
29
+
},
30
+
"cid": {
31
+
"type": "string",
32
+
"format": "cid",
33
+
"description": "The CID of the version of the record. If not specified, then return the most recent version."
34
+
}
35
+
}
36
+
},
37
+
"output": {
38
+
"encoding": "application/json",
39
+
"schema": {
40
+
"type": "object",
41
+
"required": [
42
+
"uri",
43
+
"value"
44
+
],
45
+
"properties": {
46
+
"uri": {
47
+
"type": "string",
48
+
"format": "at-uri"
49
+
},
50
+
"cid": {
51
+
"type": "string",
52
+
"format": "cid"
53
+
},
54
+
"value": {
55
+
"type": "unknown"
56
+
}
57
+
}
58
+
}
59
+
},
60
+
"errors": [
61
+
{
62
+
"name": "RecordNotFound"
63
+
}
64
+
]
65
+
}
66
+
}
67
+
}
+93
lexicons/listRecords.json
+93
lexicons/listRecords.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "com.atproto.repo.listRecords",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "List a range of records in a repository, matching a specific collection. Does not require auth.",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": [
11
+
"repo",
12
+
"collection"
13
+
],
14
+
"properties": {
15
+
"repo": {
16
+
"type": "string",
17
+
"format": "at-identifier",
18
+
"description": "The handle or DID of the repo."
19
+
},
20
+
"collection": {
21
+
"type": "string",
22
+
"format": "nsid",
23
+
"description": "The NSID of the record type."
24
+
},
25
+
"limit": {
26
+
"type": "integer",
27
+
"minimum": 1,
28
+
"maximum": 100,
29
+
"default": 50,
30
+
"description": "The number of records to return."
31
+
},
32
+
"cursor": {
33
+
"type": "string"
34
+
},
35
+
"rkeyStart": {
36
+
"type": "string",
37
+
"description": "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)"
38
+
},
39
+
"rkeyEnd": {
40
+
"type": "string",
41
+
"description": "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)"
42
+
},
43
+
"reverse": {
44
+
"type": "boolean",
45
+
"description": "Flag to reverse the order of the returned records."
46
+
}
47
+
}
48
+
},
49
+
"output": {
50
+
"encoding": "application/json",
51
+
"schema": {
52
+
"type": "object",
53
+
"required": [
54
+
"records"
55
+
],
56
+
"properties": {
57
+
"cursor": {
58
+
"type": "string"
59
+
},
60
+
"records": {
61
+
"type": "array",
62
+
"items": {
63
+
"type": "ref",
64
+
"ref": "#record"
65
+
}
66
+
}
67
+
}
68
+
}
69
+
}
70
+
},
71
+
"record": {
72
+
"type": "object",
73
+
"required": [
74
+
"uri",
75
+
"cid",
76
+
"value"
77
+
],
78
+
"properties": {
79
+
"uri": {
80
+
"type": "string",
81
+
"format": "at-uri"
82
+
},
83
+
"cid": {
84
+
"type": "string",
85
+
"format": "cid"
86
+
},
87
+
"value": {
88
+
"type": "unknown"
89
+
}
90
+
}
91
+
}
92
+
}
93
+
}
+1
-1
lexicons/paste.json
+1
-1
lexicons/paste.json
+4
package.json
+4
package.json
src/assets/NerdIosevka-Regular.woff2
src/assets/NerdIosevka-Regular.woff2
This is a binary file and will not be displayed.
+4
-4
src/auth/client.ts
+4
-4
src/auth/client.ts
···
4
4
import { SessionStore, StateStore } from "./storage";
5
5
6
6
export const createClient = async (db: Database) => {
7
-
const publicUrl = env.PUBLIC_URL;
8
-
const url = publicUrl || `http://127.0.0.1:${env.PORT}`;
7
+
const publicUrl = env.PLONK_PUBLIC_URL;
8
+
const url = publicUrl || `http://127.0.0.1:${env.PLONK_PORT}`;
9
9
const enc = encodeURIComponent;
10
10
return new NodeOAuthClient({
11
11
clientMetadata: {
12
-
client_name: "AT Protocol Express App",
12
+
client_name: "plonk.li",
13
13
client_id: publicUrl
14
14
? `${url}/client-metadata.json`
15
-
: `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`,
15
+
: `http://${env.PLONK_HOST}?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`,
16
16
client_uri: url,
17
17
redirect_uris: [`${url}/oauth/callback`],
18
18
scope: "atproto transition:generic",
+2
-2
src/db.ts
+2
-2
src/db.ts
···
47
47
indexedAt: string;
48
48
pasteUri: string;
49
49
pasteCid: string;
50
-
}
50
+
};
51
51
52
52
type AuthSessionJson = string;
53
53
type AuthStateJson = string;
···
115
115
async down(db: Kysely<unknown>) {
116
116
await db.schema.dropTable("comments").execute();
117
117
},
118
-
}
118
+
};
119
119
120
120
function generateShortString(length: number): string {
121
121
return randomBytes(length).toString("base64url").substring(0, length);
+2
-2
src/index.ts
+2
-2
src/index.ts
···
32
32
) {}
33
33
34
34
static async create() {
35
-
const db: Database = createDb(env.DB_PATH);
35
+
const db: Database = createDb(env.PLONK_DB_PATH);
36
36
await migrateToLatest(db);
37
37
const idResolver = createIdResolver();
38
38
const ctx: Ctx = {
···
55
55
app.use(router);
56
56
app.use((_req, res) => res.sendStatus(404));
57
57
58
-
const server = app.listen(env.PORT);
58
+
const server = app.listen(env.PLONK_PORT);
59
59
60
60
return new Server(app, server, ctx);
61
61
}
+8
-8
src/ingester.ts
+8
-8
src/ingester.ts
···
2
2
import { IdResolver } from "@atproto/identity";
3
3
import { Firehose } from "@atproto/sync";
4
4
import type { Database } from "#/db";
5
-
import * as Paste from "#/lexicons/types/ovh/plonk/paste";
6
-
import * as Comment from "#/lexicons/types/ovh/plonk/comment";
5
+
import * as Paste from "#/lexicons/types/li/plonk/paste";
6
+
import * as Comment from "#/lexicons/types/li/plonk/comment";
7
7
8
8
export function createIngester(db: Database, idResolver: IdResolver) {
9
9
const logger = pino({ name: "firehose ingestion" });
···
17
17
18
18
// If the write is a valid status update
19
19
if (
20
-
evt.collection === "ovh.plonk.paste" &&
20
+
evt.collection === "li.plonk.paste" &&
21
21
Paste.isRecord(record) &&
22
22
Paste.validateRecord(record).success
23
23
) {
···
43
43
)
44
44
.execute();
45
45
} else if (
46
-
evt.collection === "ovh.plonk.comment" &&
46
+
evt.collection === "li.plonk.comment" &&
47
47
Comment.isRecord(record) &&
48
48
Comment.validateRecord(record).success
49
49
) {
···
70
70
}
71
71
} else if (
72
72
evt.event === "delete" &&
73
-
evt.collection === "ovh.plonk.paste"
73
+
evt.collection === "li.plonk.paste"
74
74
) {
75
75
// Remove the status from our SQLite
76
76
await db
···
79
79
.execute();
80
80
} else if (
81
81
evt.event === "delete" &&
82
-
evt.collection === "ovh.plonk.comment"
82
+
evt.collection === "li.plonk.comment"
83
83
) {
84
84
// Remove the status from our SQLite
85
85
await db
86
86
.deleteFrom("comment")
87
87
.where("uri", "=", evt.uri.toString())
88
88
.execute();
89
-
}
89
+
}
90
90
},
91
91
onError: (err) => {
92
92
logger.error({ err }, "error on firehose ingestion");
93
93
},
94
-
filterCollections: ["ovh.plonk.paste"],
94
+
filterCollections: ["li.plonk.paste"],
95
95
excludeIdentity: true,
96
96
excludeAccount: true,
97
97
});
+105
-105
src/lexicons/index.ts
+105
-105
src/lexicons/index.ts
···
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
import {
5
-
createServer as createXrpcServer,
6
-
Server as XrpcServer,
7
-
Options as XrpcOptions,
8
-
AuthVerifier,
9
-
StreamAuthVerifier,
10
-
} from '@atproto/xrpc-server'
11
-
import { schemas } from './lexicons'
12
-
import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord'
13
-
import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords'
5
+
createServer as createXrpcServer,
6
+
Server as XrpcServer,
7
+
Options as XrpcOptions,
8
+
AuthVerifier,
9
+
StreamAuthVerifier,
10
+
} from "@atproto/xrpc-server";
11
+
import { schemas } from "./lexicons";
12
+
import * as ComAtprotoRepoGetRecord from "./types/com/atproto/repo/getRecord";
13
+
import * as ComAtprotoRepoListRecords from "./types/com/atproto/repo/listRecords";
14
14
15
15
export function createServer(options?: XrpcOptions): Server {
16
-
return new Server(options)
16
+
return new Server(options);
17
17
}
18
18
19
19
export class Server {
20
-
xrpc: XrpcServer
21
-
ovh: OvhNS
22
-
com: ComNS
23
-
app: AppNS
20
+
xrpc: XrpcServer;
21
+
li: LiNS;
22
+
com: ComNS;
23
+
app: AppNS;
24
24
25
-
constructor(options?: XrpcOptions) {
26
-
this.xrpc = createXrpcServer(schemas, options)
27
-
this.ovh = new OvhNS(this)
28
-
this.com = new ComNS(this)
29
-
this.app = new AppNS(this)
30
-
}
25
+
constructor(options?: XrpcOptions) {
26
+
this.xrpc = createXrpcServer(schemas, options);
27
+
this.li = new LiNS(this);
28
+
this.com = new ComNS(this);
29
+
this.app = new AppNS(this);
30
+
}
31
31
}
32
32
33
-
export class OvhNS {
34
-
_server: Server
35
-
plonk: OvhPlonkNS
33
+
export class LiNS {
34
+
_server: Server;
35
+
plonk: LiPlonkNS;
36
36
37
-
constructor(server: Server) {
38
-
this._server = server
39
-
this.plonk = new OvhPlonkNS(server)
40
-
}
37
+
constructor(server: Server) {
38
+
this._server = server;
39
+
this.plonk = new LiPlonkNS(server);
40
+
}
41
41
}
42
42
43
-
export class OvhPlonkNS {
44
-
_server: Server
43
+
export class LiPlonkNS {
44
+
_server: Server;
45
45
46
-
constructor(server: Server) {
47
-
this._server = server
48
-
}
46
+
constructor(server: Server) {
47
+
this._server = server;
48
+
}
49
49
}
50
50
51
51
export class ComNS {
52
-
_server: Server
53
-
atproto: ComAtprotoNS
52
+
_server: Server;
53
+
atproto: ComAtprotoNS;
54
54
55
-
constructor(server: Server) {
56
-
this._server = server
57
-
this.atproto = new ComAtprotoNS(server)
58
-
}
55
+
constructor(server: Server) {
56
+
this._server = server;
57
+
this.atproto = new ComAtprotoNS(server);
58
+
}
59
59
}
60
60
61
61
export class ComAtprotoNS {
62
-
_server: Server
63
-
repo: ComAtprotoRepoNS
62
+
_server: Server;
63
+
repo: ComAtprotoRepoNS;
64
64
65
-
constructor(server: Server) {
66
-
this._server = server
67
-
this.repo = new ComAtprotoRepoNS(server)
68
-
}
65
+
constructor(server: Server) {
66
+
this._server = server;
67
+
this.repo = new ComAtprotoRepoNS(server);
68
+
}
69
69
}
70
70
71
71
export class ComAtprotoRepoNS {
72
-
_server: Server
72
+
_server: Server;
73
73
74
-
constructor(server: Server) {
75
-
this._server = server
76
-
}
74
+
constructor(server: Server) {
75
+
this._server = server;
76
+
}
77
77
78
-
getRecord<AV extends AuthVerifier>(
79
-
cfg: ConfigOf<
80
-
AV,
81
-
ComAtprotoRepoGetRecord.Handler<ExtractAuth<AV>>,
82
-
ComAtprotoRepoGetRecord.HandlerReqCtx<ExtractAuth<AV>>
83
-
>,
84
-
) {
85
-
const nsid = 'com.atproto.repo.getRecord' // @ts-ignore
86
-
return this._server.xrpc.method(nsid, cfg)
87
-
}
78
+
getRecord<AV extends AuthVerifier>(
79
+
cfg: ConfigOf<
80
+
AV,
81
+
ComAtprotoRepoGetRecord.Handler<ExtractAuth<AV>>,
82
+
ComAtprotoRepoGetRecord.HandlerReqCtx<ExtractAuth<AV>>
83
+
>,
84
+
) {
85
+
const nsid = "com.atproto.repo.getRecord"; // @ts-ignore
86
+
return this._server.xrpc.method(nsid, cfg);
87
+
}
88
88
89
-
listRecords<AV extends AuthVerifier>(
90
-
cfg: ConfigOf<
91
-
AV,
92
-
ComAtprotoRepoListRecords.Handler<ExtractAuth<AV>>,
93
-
ComAtprotoRepoListRecords.HandlerReqCtx<ExtractAuth<AV>>
94
-
>,
95
-
) {
96
-
const nsid = 'com.atproto.repo.listRecords' // @ts-ignore
97
-
return this._server.xrpc.method(nsid, cfg)
98
-
}
89
+
listRecords<AV extends AuthVerifier>(
90
+
cfg: ConfigOf<
91
+
AV,
92
+
ComAtprotoRepoListRecords.Handler<ExtractAuth<AV>>,
93
+
ComAtprotoRepoListRecords.HandlerReqCtx<ExtractAuth<AV>>
94
+
>,
95
+
) {
96
+
const nsid = "com.atproto.repo.listRecords"; // @ts-ignore
97
+
return this._server.xrpc.method(nsid, cfg);
98
+
}
99
99
}
100
100
101
101
export class AppNS {
102
-
_server: Server
103
-
bsky: AppBskyNS
102
+
_server: Server;
103
+
bsky: AppBskyNS;
104
104
105
-
constructor(server: Server) {
106
-
this._server = server
107
-
this.bsky = new AppBskyNS(server)
108
-
}
105
+
constructor(server: Server) {
106
+
this._server = server;
107
+
this.bsky = new AppBskyNS(server);
108
+
}
109
109
}
110
110
111
111
export class AppBskyNS {
112
-
_server: Server
113
-
actor: AppBskyActorNS
112
+
_server: Server;
113
+
actor: AppBskyActorNS;
114
114
115
-
constructor(server: Server) {
116
-
this._server = server
117
-
this.actor = new AppBskyActorNS(server)
118
-
}
115
+
constructor(server: Server) {
116
+
this._server = server;
117
+
this.actor = new AppBskyActorNS(server);
118
+
}
119
119
}
120
120
121
121
export class AppBskyActorNS {
122
-
_server: Server
122
+
_server: Server;
123
123
124
-
constructor(server: Server) {
125
-
this._server = server
126
-
}
124
+
constructor(server: Server) {
125
+
this._server = server;
126
+
}
127
127
}
128
128
129
129
type SharedRateLimitOpts<T> = {
130
-
name: string
131
-
calcKey?: (ctx: T) => string
132
-
calcPoints?: (ctx: T) => number
133
-
}
130
+
name: string;
131
+
calcKey?: (ctx: T) => string;
132
+
calcPoints?: (ctx: T) => number;
133
+
};
134
134
type RouteRateLimitOpts<T> = {
135
-
durationMs: number
136
-
points: number
137
-
calcKey?: (ctx: T) => string
138
-
calcPoints?: (ctx: T) => number
139
-
}
140
-
type HandlerOpts = { blobLimit?: number }
141
-
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>
135
+
durationMs: number;
136
+
points: number;
137
+
calcKey?: (ctx: T) => string;
138
+
calcPoints?: (ctx: T) => number;
139
+
};
140
+
type HandlerOpts = { blobLimit?: number };
141
+
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>;
142
142
type ConfigOf<Auth, Handler, ReqCtx> =
143
-
| Handler
144
-
| {
145
-
auth?: Auth
146
-
opts?: HandlerOpts
147
-
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]
148
-
handler: Handler
149
-
}
143
+
| Handler
144
+
| {
145
+
auth?: Auth;
146
+
opts?: HandlerOpts;
147
+
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[];
148
+
handler: Handler;
149
+
};
150
150
type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract<
151
-
Awaited<ReturnType<AV>>,
152
-
{ credentials: unknown }
153
-
>
151
+
Awaited<ReturnType<AV>>,
152
+
{ credentials: unknown }
153
+
>;
+491
-491
src/lexicons/lexicons.ts
+491
-491
src/lexicons/lexicons.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
4
+
import { LexiconDoc, Lexicons } from "@atproto/lexicon";
5
5
6
6
export const schemaDict = {
7
-
OvhPlonkComment: {
8
-
lexicon: 1,
9
-
id: 'ovh.plonk.comment',
10
-
defs: {
11
-
main: {
12
-
type: 'record',
13
-
key: 'tid',
14
-
record: {
15
-
type: 'object',
16
-
required: ['content', 'createdAt', 'post'],
17
-
properties: {
18
-
content: {
19
-
type: 'string',
20
-
maxLength: 100000,
21
-
maxGraphemes: 10000,
22
-
description: 'comment body',
23
-
},
24
-
createdAt: {
25
-
type: 'string',
26
-
format: 'datetime',
27
-
description: 'comment creation timestamp',
28
-
},
29
-
post: {
30
-
type: 'ref',
31
-
ref: 'lex:com.atproto.repo.strongRef',
32
-
},
33
-
},
34
-
},
35
-
},
36
-
},
37
-
},
38
-
ComAtprotoLabelDefs: {
39
-
lexicon: 1,
40
-
id: 'com.atproto.label.defs',
41
-
defs: {
42
-
label: {
43
-
type: 'object',
44
-
description:
45
-
'Metadata tag on an atproto resource (eg, repo or record).',
46
-
required: ['src', 'uri', 'val', 'cts'],
47
-
properties: {
48
-
ver: {
49
-
type: 'integer',
50
-
description: 'The AT Protocol version of the label object.',
51
-
},
52
-
src: {
53
-
type: 'string',
54
-
format: 'did',
55
-
description: 'DID of the actor who created this label.',
56
-
},
57
-
uri: {
58
-
type: 'string',
59
-
format: 'uri',
60
-
description:
61
-
'AT URI of the record, repository (account), or other resource that this label applies to.',
62
-
},
63
-
cid: {
64
-
type: 'string',
65
-
format: 'cid',
66
-
description:
67
-
"Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
68
-
},
69
-
val: {
70
-
type: 'string',
71
-
maxLength: 128,
72
-
description:
73
-
'The short string name of the value or type of this label.',
74
-
},
75
-
neg: {
76
-
type: 'boolean',
77
-
description:
78
-
'If true, this is a negation label, overwriting a previous label.',
79
-
},
80
-
cts: {
81
-
type: 'string',
82
-
format: 'datetime',
83
-
description: 'Timestamp when this label was created.',
84
-
},
85
-
exp: {
86
-
type: 'string',
87
-
format: 'datetime',
88
-
description:
89
-
'Timestamp at which this label expires (no longer applies).',
90
-
},
91
-
sig: {
92
-
type: 'bytes',
93
-
description: 'Signature of dag-cbor encoded label.',
94
-
},
95
-
},
96
-
},
97
-
selfLabels: {
98
-
type: 'object',
99
-
description:
100
-
'Metadata tags on an atproto record, published by the author within the record.',
101
-
required: ['values'],
102
-
properties: {
103
-
values: {
104
-
type: 'array',
105
-
items: {
106
-
type: 'ref',
107
-
ref: 'lex:com.atproto.label.defs#selfLabel',
108
-
},
109
-
maxLength: 10,
110
-
},
111
-
},
112
-
},
113
-
selfLabel: {
114
-
type: 'object',
115
-
description:
116
-
'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.',
117
-
required: ['val'],
118
-
properties: {
119
-
val: {
120
-
type: 'string',
121
-
maxLength: 128,
122
-
description:
123
-
'The short string name of the value or type of this label.',
124
-
},
125
-
},
126
-
},
127
-
labelValueDefinition: {
128
-
type: 'object',
129
-
description:
130
-
'Declares a label value and its expected interpretations and behaviors.',
131
-
required: ['identifier', 'severity', 'blurs', 'locales'],
132
-
properties: {
133
-
identifier: {
134
-
type: 'string',
135
-
description:
136
-
"The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
137
-
maxLength: 100,
138
-
maxGraphemes: 100,
139
-
},
140
-
severity: {
141
-
type: 'string',
142
-
description:
143
-
"How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
144
-
knownValues: ['inform', 'alert', 'none'],
145
-
},
146
-
blurs: {
147
-
type: 'string',
148
-
description:
149
-
"What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
150
-
knownValues: ['content', 'media', 'none'],
151
-
},
152
-
defaultSetting: {
153
-
type: 'string',
154
-
description: 'The default setting for this label.',
155
-
knownValues: ['ignore', 'warn', 'hide'],
156
-
default: 'warn',
157
-
},
158
-
adultOnly: {
159
-
type: 'boolean',
160
-
description:
161
-
'Does the user need to have adult content enabled in order to configure this label?',
162
-
},
163
-
locales: {
164
-
type: 'array',
165
-
items: {
166
-
type: 'ref',
167
-
ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings',
168
-
},
169
-
},
170
-
},
171
-
},
172
-
labelValueDefinitionStrings: {
173
-
type: 'object',
174
-
description:
175
-
'Strings which describe the label in the UI, localized into a specific language.',
176
-
required: ['lang', 'name', 'description'],
177
-
properties: {
178
-
lang: {
179
-
type: 'string',
180
-
description:
181
-
'The code of the language these strings are written in.',
182
-
format: 'language',
183
-
},
184
-
name: {
185
-
type: 'string',
186
-
description: 'A short human-readable name for the label.',
187
-
maxGraphemes: 64,
188
-
maxLength: 640,
189
-
},
190
-
description: {
191
-
type: 'string',
192
-
description:
193
-
'A longer description of what the label means and why it might be applied.',
194
-
maxGraphemes: 10000,
195
-
maxLength: 100000,
196
-
},
197
-
},
198
-
},
199
-
labelValue: {
200
-
type: 'string',
201
-
knownValues: [
202
-
'!hide',
203
-
'!no-promote',
204
-
'!warn',
205
-
'!no-unauthenticated',
206
-
'dmca-violation',
207
-
'doxxing',
208
-
'porn',
209
-
'sexual',
210
-
'nudity',
211
-
'nsfl',
212
-
'gore',
213
-
],
214
-
},
215
-
},
216
-
},
217
-
ComAtprotoRepoGetRecord: {
218
-
lexicon: 1,
219
-
id: 'com.atproto.repo.getRecord',
220
-
defs: {
221
-
main: {
222
-
type: 'query',
223
-
description:
224
-
'Get a single record from a repository. Does not require auth.',
225
-
parameters: {
226
-
type: 'params',
227
-
required: ['repo', 'collection', 'rkey'],
228
-
properties: {
229
-
repo: {
230
-
type: 'string',
231
-
format: 'at-identifier',
232
-
description: 'The handle or DID of the repo.',
233
-
},
234
-
collection: {
235
-
type: 'string',
236
-
format: 'nsid',
237
-
description: 'The NSID of the record collection.',
238
-
},
239
-
rkey: {
240
-
type: 'string',
241
-
description: 'The Record Key.',
242
-
},
243
-
cid: {
244
-
type: 'string',
245
-
format: 'cid',
246
-
description:
247
-
'The CID of the version of the record. If not specified, then return the most recent version.',
248
-
},
249
-
},
250
-
},
251
-
output: {
252
-
encoding: 'application/json',
253
-
schema: {
254
-
type: 'object',
255
-
required: ['uri', 'value'],
256
-
properties: {
257
-
uri: {
258
-
type: 'string',
259
-
format: 'at-uri',
260
-
},
261
-
cid: {
262
-
type: 'string',
263
-
format: 'cid',
264
-
},
265
-
value: {
266
-
type: 'unknown',
267
-
},
268
-
},
269
-
},
270
-
},
271
-
errors: [
272
-
{
273
-
name: 'RecordNotFound',
274
-
},
275
-
],
276
-
},
277
-
},
278
-
},
279
-
ComAtprotoRepoListRecords: {
280
-
lexicon: 1,
281
-
id: 'com.atproto.repo.listRecords',
282
-
defs: {
283
-
main: {
284
-
type: 'query',
285
-
description:
286
-
'List a range of records in a repository, matching a specific collection. Does not require auth.',
287
-
parameters: {
288
-
type: 'params',
289
-
required: ['repo', 'collection'],
290
-
properties: {
291
-
repo: {
292
-
type: 'string',
293
-
format: 'at-identifier',
294
-
description: 'The handle or DID of the repo.',
295
-
},
296
-
collection: {
297
-
type: 'string',
298
-
format: 'nsid',
299
-
description: 'The NSID of the record type.',
300
-
},
301
-
limit: {
302
-
type: 'integer',
303
-
minimum: 1,
304
-
maximum: 100,
305
-
default: 50,
306
-
description: 'The number of records to return.',
307
-
},
308
-
cursor: {
309
-
type: 'string',
310
-
},
311
-
rkeyStart: {
312
-
type: 'string',
313
-
description:
314
-
'DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)',
315
-
},
316
-
rkeyEnd: {
317
-
type: 'string',
318
-
description:
319
-
'DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)',
320
-
},
321
-
reverse: {
322
-
type: 'boolean',
323
-
description: 'Flag to reverse the order of the returned records.',
324
-
},
325
-
},
326
-
},
327
-
output: {
328
-
encoding: 'application/json',
329
-
schema: {
330
-
type: 'object',
331
-
required: ['records'],
332
-
properties: {
333
-
cursor: {
334
-
type: 'string',
335
-
},
336
-
records: {
337
-
type: 'array',
338
-
items: {
339
-
type: 'ref',
340
-
ref: 'lex:com.atproto.repo.listRecords#record',
341
-
},
342
-
},
343
-
},
344
-
},
345
-
},
346
-
},
347
-
record: {
348
-
type: 'object',
349
-
required: ['uri', 'cid', 'value'],
350
-
properties: {
351
-
uri: {
352
-
type: 'string',
353
-
format: 'at-uri',
354
-
},
355
-
cid: {
356
-
type: 'string',
357
-
format: 'cid',
358
-
},
359
-
value: {
360
-
type: 'unknown',
361
-
},
362
-
},
363
-
},
364
-
},
365
-
},
366
-
OvhPlonkPaste: {
367
-
lexicon: 1,
368
-
id: 'ovh.plonk.paste',
369
-
defs: {
370
-
main: {
371
-
type: 'record',
372
-
key: 'tid',
373
-
record: {
374
-
type: 'object',
375
-
required: ['code', 'shortUrl', 'lang', 'title', 'createdAt'],
376
-
properties: {
377
-
code: {
378
-
type: 'string',
379
-
minLength: 1,
380
-
maxGraphemes: 65536,
381
-
maxLength: 65536,
382
-
},
383
-
shortUrl: {
384
-
type: 'string',
385
-
minLength: 2,
386
-
maxGraphemes: 10,
387
-
maxLength: 10,
388
-
},
389
-
lang: {
390
-
type: 'string',
391
-
minLength: 1,
392
-
maxGraphemes: 20,
393
-
maxLength: 20,
394
-
},
395
-
title: {
396
-
type: 'string',
397
-
minLength: 1,
398
-
maxGraphemes: 100,
399
-
maxLength: 100,
400
-
},
401
-
createdAt: {
402
-
type: 'string',
403
-
format: 'datetime',
404
-
},
405
-
},
406
-
},
407
-
},
408
-
},
409
-
},
410
-
AppBskyActorProfile: {
411
-
lexicon: 1,
412
-
id: 'app.bsky.actor.profile',
413
-
defs: {
414
-
main: {
415
-
type: 'record',
416
-
description: 'A declaration of a Bluesky account profile.',
417
-
key: 'literal:self',
418
-
record: {
419
-
type: 'object',
420
-
properties: {
421
-
displayName: {
422
-
type: 'string',
423
-
maxGraphemes: 64,
424
-
maxLength: 640,
425
-
},
426
-
description: {
427
-
type: 'string',
428
-
description: 'Free-form profile description text.',
429
-
maxGraphemes: 256,
430
-
maxLength: 2560,
431
-
},
432
-
avatar: {
433
-
type: 'blob',
434
-
description:
435
-
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
436
-
accept: ['image/png', 'image/jpeg'],
437
-
maxSize: 1000000,
438
-
},
439
-
banner: {
440
-
type: 'blob',
441
-
description:
442
-
'Larger horizontal image to display behind profile view.',
443
-
accept: ['image/png', 'image/jpeg'],
444
-
maxSize: 1000000,
445
-
},
446
-
labels: {
447
-
type: 'union',
448
-
description:
449
-
'Self-label values, specific to the Bluesky application, on the overall account.',
450
-
refs: ['lex:com.atproto.label.defs#selfLabels'],
451
-
},
452
-
joinedViaStarterPack: {
453
-
type: 'ref',
454
-
ref: 'lex:com.atproto.repo.strongRef',
455
-
},
456
-
createdAt: {
457
-
type: 'string',
458
-
format: 'datetime',
459
-
},
460
-
},
461
-
},
462
-
},
463
-
},
464
-
},
465
-
ComAtprotoRepoStrongRef: {
466
-
lexicon: 1,
467
-
id: 'com.atproto.repo.strongRef',
468
-
description: 'A URI with a content-hash fingerprint.',
469
-
defs: {
470
-
main: {
471
-
type: 'object',
472
-
required: ['uri', 'cid'],
473
-
properties: {
474
-
uri: {
475
-
type: 'string',
476
-
format: 'at-uri',
477
-
},
478
-
cid: {
479
-
type: 'string',
480
-
format: 'cid',
481
-
},
482
-
},
483
-
},
484
-
},
485
-
},
486
-
}
487
-
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
488
-
export const lexicons: Lexicons = new Lexicons(schemas)
7
+
LiPlonkComment: {
8
+
lexicon: 1,
9
+
id: "li.plonk.comment",
10
+
defs: {
11
+
main: {
12
+
type: "record",
13
+
key: "tid",
14
+
record: {
15
+
type: "object",
16
+
required: ["content", "createdAt", "post"],
17
+
properties: {
18
+
content: {
19
+
type: "string",
20
+
maxLength: 100000,
21
+
maxGraphemes: 10000,
22
+
description: "comment body",
23
+
},
24
+
createdAt: {
25
+
type: "string",
26
+
format: "datetime",
27
+
description: "comment creation timestamp",
28
+
},
29
+
post: {
30
+
type: "ref",
31
+
ref: "lex:com.atproto.repo.strongRef",
32
+
},
33
+
},
34
+
},
35
+
},
36
+
},
37
+
},
38
+
ComAtprotoLabelDefs: {
39
+
lexicon: 1,
40
+
id: "com.atproto.label.defs",
41
+
defs: {
42
+
label: {
43
+
type: "object",
44
+
description:
45
+
"Metadata tag on an atproto resource (eg, repo or record).",
46
+
required: ["src", "uri", "val", "cts"],
47
+
properties: {
48
+
ver: {
49
+
type: "integer",
50
+
description: "The AT Protocol version of the label object.",
51
+
},
52
+
src: {
53
+
type: "string",
54
+
format: "did",
55
+
description: "DID of the actor who created this label.",
56
+
},
57
+
uri: {
58
+
type: "string",
59
+
format: "uri",
60
+
description:
61
+
"AT URI of the record, repository (account), or other resource that this label applies to.",
62
+
},
63
+
cid: {
64
+
type: "string",
65
+
format: "cid",
66
+
description:
67
+
"Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
68
+
},
69
+
val: {
70
+
type: "string",
71
+
maxLength: 128,
72
+
description:
73
+
"The short string name of the value or type of this label.",
74
+
},
75
+
neg: {
76
+
type: "boolean",
77
+
description:
78
+
"If true, this is a negation label, overwriting a previous label.",
79
+
},
80
+
cts: {
81
+
type: "string",
82
+
format: "datetime",
83
+
description: "Timestamp when this label was created.",
84
+
},
85
+
exp: {
86
+
type: "string",
87
+
format: "datetime",
88
+
description:
89
+
"Timestamp at which this label expires (no longer applies).",
90
+
},
91
+
sig: {
92
+
type: "bytes",
93
+
description: "Signature of dag-cbor encoded label.",
94
+
},
95
+
},
96
+
},
97
+
selfLabels: {
98
+
type: "object",
99
+
description:
100
+
"Metadata tags on an atproto record, published by the author within the record.",
101
+
required: ["values"],
102
+
properties: {
103
+
values: {
104
+
type: "array",
105
+
items: {
106
+
type: "ref",
107
+
ref: "lex:com.atproto.label.defs#selfLabel",
108
+
},
109
+
maxLength: 10,
110
+
},
111
+
},
112
+
},
113
+
selfLabel: {
114
+
type: "object",
115
+
description:
116
+
"Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.",
117
+
required: ["val"],
118
+
properties: {
119
+
val: {
120
+
type: "string",
121
+
maxLength: 128,
122
+
description:
123
+
"The short string name of the value or type of this label.",
124
+
},
125
+
},
126
+
},
127
+
labelValueDefinition: {
128
+
type: "object",
129
+
description:
130
+
"Declares a label value and its expected interpretations and behaviors.",
131
+
required: ["identifier", "severity", "blurs", "locales"],
132
+
properties: {
133
+
identifier: {
134
+
type: "string",
135
+
description:
136
+
"The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
137
+
maxLength: 100,
138
+
maxGraphemes: 100,
139
+
},
140
+
severity: {
141
+
type: "string",
142
+
description:
143
+
"How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
144
+
knownValues: ["inform", "alert", "none"],
145
+
},
146
+
blurs: {
147
+
type: "string",
148
+
description:
149
+
"What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
150
+
knownValues: ["content", "media", "none"],
151
+
},
152
+
defaultSetting: {
153
+
type: "string",
154
+
description: "The default setting for this label.",
155
+
knownValues: ["ignore", "warn", "hide"],
156
+
default: "warn",
157
+
},
158
+
adultOnly: {
159
+
type: "boolean",
160
+
description:
161
+
"Does the user need to have adult content enabled in order to configure this label?",
162
+
},
163
+
locales: {
164
+
type: "array",
165
+
items: {
166
+
type: "ref",
167
+
ref: "lex:com.atproto.label.defs#labelValueDefinitionStrings",
168
+
},
169
+
},
170
+
},
171
+
},
172
+
labelValueDefinitionStrings: {
173
+
type: "object",
174
+
description:
175
+
"Strings which describe the label in the UI, localized into a specific language.",
176
+
required: ["lang", "name", "description"],
177
+
properties: {
178
+
lang: {
179
+
type: "string",
180
+
description:
181
+
"The code of the language these strings are written in.",
182
+
format: "language",
183
+
},
184
+
name: {
185
+
type: "string",
186
+
description: "A short human-readable name for the label.",
187
+
maxGraphemes: 64,
188
+
maxLength: 640,
189
+
},
190
+
description: {
191
+
type: "string",
192
+
description:
193
+
"A longer description of what the label means and why it might be applied.",
194
+
maxGraphemes: 10000,
195
+
maxLength: 100000,
196
+
},
197
+
},
198
+
},
199
+
labelValue: {
200
+
type: "string",
201
+
knownValues: [
202
+
"!hide",
203
+
"!no-promote",
204
+
"!warn",
205
+
"!no-unauthenticated",
206
+
"dmca-violation",
207
+
"doxxing",
208
+
"porn",
209
+
"sexual",
210
+
"nudity",
211
+
"nsfl",
212
+
"gore",
213
+
],
214
+
},
215
+
},
216
+
},
217
+
ComAtprotoRepoGetRecord: {
218
+
lexicon: 1,
219
+
id: "com.atproto.repo.getRecord",
220
+
defs: {
221
+
main: {
222
+
type: "query",
223
+
description:
224
+
"Get a single record from a repository. Does not require auth.",
225
+
parameters: {
226
+
type: "params",
227
+
required: ["repo", "collection", "rkey"],
228
+
properties: {
229
+
repo: {
230
+
type: "string",
231
+
format: "at-identifier",
232
+
description: "The handle or DID of the repo.",
233
+
},
234
+
collection: {
235
+
type: "string",
236
+
format: "nsid",
237
+
description: "The NSID of the record collection.",
238
+
},
239
+
rkey: {
240
+
type: "string",
241
+
description: "The Record Key.",
242
+
},
243
+
cid: {
244
+
type: "string",
245
+
format: "cid",
246
+
description:
247
+
"The CID of the version of the record. If not specified, then return the most recent version.",
248
+
},
249
+
},
250
+
},
251
+
output: {
252
+
encoding: "application/json",
253
+
schema: {
254
+
type: "object",
255
+
required: ["uri", "value"],
256
+
properties: {
257
+
uri: {
258
+
type: "string",
259
+
format: "at-uri",
260
+
},
261
+
cid: {
262
+
type: "string",
263
+
format: "cid",
264
+
},
265
+
value: {
266
+
type: "unknown",
267
+
},
268
+
},
269
+
},
270
+
},
271
+
errors: [
272
+
{
273
+
name: "RecordNotFound",
274
+
},
275
+
],
276
+
},
277
+
},
278
+
},
279
+
ComAtprotoRepoListRecords: {
280
+
lexicon: 1,
281
+
id: "com.atproto.repo.listRecords",
282
+
defs: {
283
+
main: {
284
+
type: "query",
285
+
description:
286
+
"List a range of records in a repository, matching a specific collection. Does not require auth.",
287
+
parameters: {
288
+
type: "params",
289
+
required: ["repo", "collection"],
290
+
properties: {
291
+
repo: {
292
+
type: "string",
293
+
format: "at-identifier",
294
+
description: "The handle or DID of the repo.",
295
+
},
296
+
collection: {
297
+
type: "string",
298
+
format: "nsid",
299
+
description: "The NSID of the record type.",
300
+
},
301
+
limit: {
302
+
type: "integer",
303
+
minimum: 1,
304
+
maximum: 100,
305
+
default: 50,
306
+
description: "The number of records to return.",
307
+
},
308
+
cursor: {
309
+
type: "string",
310
+
},
311
+
rkeyStart: {
312
+
type: "string",
313
+
description:
314
+
"DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)",
315
+
},
316
+
rkeyEnd: {
317
+
type: "string",
318
+
description:
319
+
"DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)",
320
+
},
321
+
reverse: {
322
+
type: "boolean",
323
+
description: "Flag to reverse the order of the returned records.",
324
+
},
325
+
},
326
+
},
327
+
output: {
328
+
encoding: "application/json",
329
+
schema: {
330
+
type: "object",
331
+
required: ["records"],
332
+
properties: {
333
+
cursor: {
334
+
type: "string",
335
+
},
336
+
records: {
337
+
type: "array",
338
+
items: {
339
+
type: "ref",
340
+
ref: "lex:com.atproto.repo.listRecords#record",
341
+
},
342
+
},
343
+
},
344
+
},
345
+
},
346
+
},
347
+
record: {
348
+
type: "object",
349
+
required: ["uri", "cid", "value"],
350
+
properties: {
351
+
uri: {
352
+
type: "string",
353
+
format: "at-uri",
354
+
},
355
+
cid: {
356
+
type: "string",
357
+
format: "cid",
358
+
},
359
+
value: {
360
+
type: "unknown",
361
+
},
362
+
},
363
+
},
364
+
},
365
+
},
366
+
LiPlonkPaste: {
367
+
lexicon: 1,
368
+
id: "li.plonk.paste",
369
+
defs: {
370
+
main: {
371
+
type: "record",
372
+
key: "tid",
373
+
record: {
374
+
type: "object",
375
+
required: ["code", "shortUrl", "lang", "title", "createdAt"],
376
+
properties: {
377
+
code: {
378
+
type: "string",
379
+
minLength: 1,
380
+
maxGraphemes: 65536,
381
+
maxLength: 65536,
382
+
},
383
+
shortUrl: {
384
+
type: "string",
385
+
minLength: 2,
386
+
maxGraphemes: 10,
387
+
maxLength: 10,
388
+
},
389
+
lang: {
390
+
type: "string",
391
+
minLength: 1,
392
+
maxGraphemes: 20,
393
+
maxLength: 20,
394
+
},
395
+
title: {
396
+
type: "string",
397
+
minLength: 1,
398
+
maxGraphemes: 100,
399
+
maxLength: 100,
400
+
},
401
+
createdAt: {
402
+
type: "string",
403
+
format: "datetime",
404
+
},
405
+
},
406
+
},
407
+
},
408
+
},
409
+
},
410
+
AppBskyActorProfile: {
411
+
lexicon: 1,
412
+
id: "app.bsky.actor.profile",
413
+
defs: {
414
+
main: {
415
+
type: "record",
416
+
description: "A declaration of a Bluesky account profile.",
417
+
key: "literal:self",
418
+
record: {
419
+
type: "object",
420
+
properties: {
421
+
displayName: {
422
+
type: "string",
423
+
maxGraphemes: 64,
424
+
maxLength: 640,
425
+
},
426
+
description: {
427
+
type: "string",
428
+
description: "Free-form profile description text.",
429
+
maxGraphemes: 256,
430
+
maxLength: 2560,
431
+
},
432
+
avatar: {
433
+
type: "blob",
434
+
description:
435
+
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
436
+
accept: ["image/png", "image/jpeg"],
437
+
maxSize: 1000000,
438
+
},
439
+
banner: {
440
+
type: "blob",
441
+
description:
442
+
"Larger horizontal image to display behind profile view.",
443
+
accept: ["image/png", "image/jpeg"],
444
+
maxSize: 1000000,
445
+
},
446
+
labels: {
447
+
type: "union",
448
+
description:
449
+
"Self-label values, specific to the Bluesky application, on the overall account.",
450
+
refs: ["lex:com.atproto.label.defs#selfLabels"],
451
+
},
452
+
joinedViaStarterPack: {
453
+
type: "ref",
454
+
ref: "lex:com.atproto.repo.strongRef",
455
+
},
456
+
createdAt: {
457
+
type: "string",
458
+
format: "datetime",
459
+
},
460
+
},
461
+
},
462
+
},
463
+
},
464
+
},
465
+
ComAtprotoRepoStrongRef: {
466
+
lexicon: 1,
467
+
id: "com.atproto.repo.strongRef",
468
+
description: "A URI with a content-hash fingerprint.",
469
+
defs: {
470
+
main: {
471
+
type: "object",
472
+
required: ["uri", "cid"],
473
+
properties: {
474
+
uri: {
475
+
type: "string",
476
+
format: "at-uri",
477
+
},
478
+
cid: {
479
+
type: "string",
480
+
format: "cid",
481
+
},
482
+
},
483
+
},
484
+
},
485
+
},
486
+
};
487
+
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[];
488
+
export const lexicons: Lexicons = new Lexicons(schemas);
489
489
export const ids = {
490
-
OvhPlonkComment: 'ovh.plonk.comment',
491
-
ComAtprotoLabelDefs: 'com.atproto.label.defs',
492
-
ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord',
493
-
ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords',
494
-
OvhPlonkPaste: 'ovh.plonk.paste',
495
-
AppBskyActorProfile: 'app.bsky.actor.profile',
496
-
ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef',
497
-
}
490
+
LiPlonkComment: "li.plonk.comment",
491
+
ComAtprotoLabelDefs: "com.atproto.label.defs",
492
+
ComAtprotoRepoGetRecord: "com.atproto.repo.getRecord",
493
+
ComAtprotoRepoListRecords: "com.atproto.repo.listRecords",
494
+
LiPlonkPaste: "li.plonk.paste",
495
+
AppBskyActorProfile: "app.bsky.actor.profile",
496
+
ComAtprotoRepoStrongRef: "com.atproto.repo.strongRef",
497
+
};
+26
-26
src/lexicons/types/app/bsky/actor/profile.ts
+26
-26
src/lexicons/types/app/bsky/actor/profile.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../../lexicons'
6
-
import { isObj, hasProp } from '../../../../util'
7
-
import { CID } from 'multiformats/cid'
8
-
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
9
-
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../../lexicons";
6
+
import { isObj, hasProp } from "../../../../util";
7
+
import { CID } from "multiformats/cid";
8
+
import * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs";
9
+
import * as ComAtprotoRepoStrongRef from "../../../com/atproto/repo/strongRef";
10
10
11
11
export interface Record {
12
-
displayName?: string
13
-
/** Free-form profile description text. */
14
-
description?: string
15
-
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
16
-
avatar?: BlobRef
17
-
/** Larger horizontal image to display behind profile view. */
18
-
banner?: BlobRef
19
-
labels?:
20
-
| ComAtprotoLabelDefs.SelfLabels
21
-
| { $type: string; [k: string]: unknown }
22
-
joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main
23
-
createdAt?: string
24
-
[k: string]: unknown
12
+
displayName?: string;
13
+
/** Free-form profile description text. */
14
+
description?: string;
15
+
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
16
+
avatar?: BlobRef;
17
+
/** Larger horizontal image to display behind profile view. */
18
+
banner?: BlobRef;
19
+
labels?:
20
+
| ComAtprotoLabelDefs.SelfLabels
21
+
| { $type: string; [k: string]: unknown };
22
+
joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main;
23
+
createdAt?: string;
24
+
[k: string]: unknown;
25
25
}
26
26
27
27
export function isRecord(v: unknown): v is Record {
28
-
return (
29
-
isObj(v) &&
30
-
hasProp(v, '$type') &&
31
-
(v.$type === 'app.bsky.actor.profile#main' ||
32
-
v.$type === 'app.bsky.actor.profile')
33
-
)
28
+
return (
29
+
isObj(v) &&
30
+
hasProp(v, "$type") &&
31
+
(v.$type === "app.bsky.actor.profile#main" ||
32
+
v.$type === "app.bsky.actor.profile")
33
+
);
34
34
}
35
35
36
36
export function validateRecord(v: unknown): ValidationResult {
37
-
return lexicons.validate('app.bsky.actor.profile#main', v)
37
+
return lexicons.validate("app.bsky.actor.profile#main", v);
38
38
}
+94
-94
src/lexicons/types/com/atproto/label/defs.ts
+94
-94
src/lexicons/types/com/atproto/label/defs.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../../lexicons'
6
-
import { isObj, hasProp } from '../../../../util'
7
-
import { CID } from 'multiformats/cid'
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../../lexicons";
6
+
import { isObj, hasProp } from "../../../../util";
7
+
import { CID } from "multiformats/cid";
8
8
9
9
/** Metadata tag on an atproto resource (eg, repo or record). */
10
10
export interface Label {
11
-
/** The AT Protocol version of the label object. */
12
-
ver?: number
13
-
/** DID of the actor who created this label. */
14
-
src: string
15
-
/** AT URI of the record, repository (account), or other resource that this label applies to. */
16
-
uri: string
17
-
/** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */
18
-
cid?: string
19
-
/** The short string name of the value or type of this label. */
20
-
val: string
21
-
/** If true, this is a negation label, overwriting a previous label. */
22
-
neg?: boolean
23
-
/** Timestamp when this label was created. */
24
-
cts: string
25
-
/** Timestamp at which this label expires (no longer applies). */
26
-
exp?: string
27
-
/** Signature of dag-cbor encoded label. */
28
-
sig?: Uint8Array
29
-
[k: string]: unknown
11
+
/** The AT Protocol version of the label object. */
12
+
ver?: number;
13
+
/** DID of the actor who created this label. */
14
+
src: string;
15
+
/** AT URI of the record, repository (account), or other resource that this label applies to. */
16
+
uri: string;
17
+
/** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */
18
+
cid?: string;
19
+
/** The short string name of the value or type of this label. */
20
+
val: string;
21
+
/** If true, this is a negation label, overwriting a previous label. */
22
+
neg?: boolean;
23
+
/** Timestamp when this label was created. */
24
+
cts: string;
25
+
/** Timestamp at which this label expires (no longer applies). */
26
+
exp?: string;
27
+
/** Signature of dag-cbor encoded label. */
28
+
sig?: Uint8Array;
29
+
[k: string]: unknown;
30
30
}
31
31
32
32
export function isLabel(v: unknown): v is Label {
33
-
return (
34
-
isObj(v) &&
35
-
hasProp(v, '$type') &&
36
-
v.$type === 'com.atproto.label.defs#label'
37
-
)
33
+
return (
34
+
isObj(v) &&
35
+
hasProp(v, "$type") &&
36
+
v.$type === "com.atproto.label.defs#label"
37
+
);
38
38
}
39
39
40
40
export function validateLabel(v: unknown): ValidationResult {
41
-
return lexicons.validate('com.atproto.label.defs#label', v)
41
+
return lexicons.validate("com.atproto.label.defs#label", v);
42
42
}
43
43
44
44
/** Metadata tags on an atproto record, published by the author within the record. */
45
45
export interface SelfLabels {
46
-
values: SelfLabel[]
47
-
[k: string]: unknown
46
+
values: SelfLabel[];
47
+
[k: string]: unknown;
48
48
}
49
49
50
50
export function isSelfLabels(v: unknown): v is SelfLabels {
51
-
return (
52
-
isObj(v) &&
53
-
hasProp(v, '$type') &&
54
-
v.$type === 'com.atproto.label.defs#selfLabels'
55
-
)
51
+
return (
52
+
isObj(v) &&
53
+
hasProp(v, "$type") &&
54
+
v.$type === "com.atproto.label.defs#selfLabels"
55
+
);
56
56
}
57
57
58
58
export function validateSelfLabels(v: unknown): ValidationResult {
59
-
return lexicons.validate('com.atproto.label.defs#selfLabels', v)
59
+
return lexicons.validate("com.atproto.label.defs#selfLabels", v);
60
60
}
61
61
62
62
/** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */
63
63
export interface SelfLabel {
64
-
/** The short string name of the value or type of this label. */
65
-
val: string
66
-
[k: string]: unknown
64
+
/** The short string name of the value or type of this label. */
65
+
val: string;
66
+
[k: string]: unknown;
67
67
}
68
68
69
69
export function isSelfLabel(v: unknown): v is SelfLabel {
70
-
return (
71
-
isObj(v) &&
72
-
hasProp(v, '$type') &&
73
-
v.$type === 'com.atproto.label.defs#selfLabel'
74
-
)
70
+
return (
71
+
isObj(v) &&
72
+
hasProp(v, "$type") &&
73
+
v.$type === "com.atproto.label.defs#selfLabel"
74
+
);
75
75
}
76
76
77
77
export function validateSelfLabel(v: unknown): ValidationResult {
78
-
return lexicons.validate('com.atproto.label.defs#selfLabel', v)
78
+
return lexicons.validate("com.atproto.label.defs#selfLabel", v);
79
79
}
80
80
81
81
/** Declares a label value and its expected interpretations and behaviors. */
82
82
export interface LabelValueDefinition {
83
-
/** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */
84
-
identifier: string
85
-
/** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */
86
-
severity: 'inform' | 'alert' | 'none' | (string & {})
87
-
/** What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. */
88
-
blurs: 'content' | 'media' | 'none' | (string & {})
89
-
/** The default setting for this label. */
90
-
defaultSetting: 'ignore' | 'warn' | 'hide' | (string & {})
91
-
/** Does the user need to have adult content enabled in order to configure this label? */
92
-
adultOnly?: boolean
93
-
locales: LabelValueDefinitionStrings[]
94
-
[k: string]: unknown
83
+
/** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */
84
+
identifier: string;
85
+
/** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */
86
+
severity: "inform" | "alert" | "none" | (string & {});
87
+
/** What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. */
88
+
blurs: "content" | "media" | "none" | (string & {});
89
+
/** The default setting for this label. */
90
+
defaultSetting: "ignore" | "warn" | "hide" | (string & {});
91
+
/** Does the user need to have adult content enabled in order to configure this label? */
92
+
adultOnly?: boolean;
93
+
locales: LabelValueDefinitionStrings[];
94
+
[k: string]: unknown;
95
95
}
96
96
97
97
export function isLabelValueDefinition(v: unknown): v is LabelValueDefinition {
98
-
return (
99
-
isObj(v) &&
100
-
hasProp(v, '$type') &&
101
-
v.$type === 'com.atproto.label.defs#labelValueDefinition'
102
-
)
98
+
return (
99
+
isObj(v) &&
100
+
hasProp(v, "$type") &&
101
+
v.$type === "com.atproto.label.defs#labelValueDefinition"
102
+
);
103
103
}
104
104
105
105
export function validateLabelValueDefinition(v: unknown): ValidationResult {
106
-
return lexicons.validate('com.atproto.label.defs#labelValueDefinition', v)
106
+
return lexicons.validate("com.atproto.label.defs#labelValueDefinition", v);
107
107
}
108
108
109
109
/** Strings which describe the label in the UI, localized into a specific language. */
110
110
export interface LabelValueDefinitionStrings {
111
-
/** The code of the language these strings are written in. */
112
-
lang: string
113
-
/** A short human-readable name for the label. */
114
-
name: string
115
-
/** A longer description of what the label means and why it might be applied. */
116
-
description: string
117
-
[k: string]: unknown
111
+
/** The code of the language these strings are written in. */
112
+
lang: string;
113
+
/** A short human-readable name for the label. */
114
+
name: string;
115
+
/** A longer description of what the label means and why it might be applied. */
116
+
description: string;
117
+
[k: string]: unknown;
118
118
}
119
119
120
120
export function isLabelValueDefinitionStrings(
121
-
v: unknown,
121
+
v: unknown,
122
122
): v is LabelValueDefinitionStrings {
123
-
return (
124
-
isObj(v) &&
125
-
hasProp(v, '$type') &&
126
-
v.$type === 'com.atproto.label.defs#labelValueDefinitionStrings'
127
-
)
123
+
return (
124
+
isObj(v) &&
125
+
hasProp(v, "$type") &&
126
+
v.$type === "com.atproto.label.defs#labelValueDefinitionStrings"
127
+
);
128
128
}
129
129
130
130
export function validateLabelValueDefinitionStrings(
131
-
v: unknown,
131
+
v: unknown,
132
132
): ValidationResult {
133
-
return lexicons.validate(
134
-
'com.atproto.label.defs#labelValueDefinitionStrings',
135
-
v,
136
-
)
133
+
return lexicons.validate(
134
+
"com.atproto.label.defs#labelValueDefinitionStrings",
135
+
v,
136
+
);
137
137
}
138
138
139
139
export type LabelValue =
140
-
| '!hide'
141
-
| '!no-promote'
142
-
| '!warn'
143
-
| '!no-unauthenticated'
144
-
| 'dmca-violation'
145
-
| 'doxxing'
146
-
| 'porn'
147
-
| 'sexual'
148
-
| 'nudity'
149
-
| 'nsfl'
150
-
| 'gore'
151
-
| (string & {})
140
+
| "!hide"
141
+
| "!no-promote"
142
+
| "!warn"
143
+
| "!no-unauthenticated"
144
+
| "dmca-violation"
145
+
| "doxxing"
146
+
| "porn"
147
+
| "sexual"
148
+
| "nudity"
149
+
| "nsfl"
150
+
| "gore"
151
+
| (string & {});
+35
-35
src/lexicons/types/com/atproto/repo/getRecord.ts
+35
-35
src/lexicons/types/com/atproto/repo/getRecord.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import express from 'express'
5
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
-
import { lexicons } from '../../../../lexicons'
7
-
import { isObj, hasProp } from '../../../../util'
8
-
import { CID } from 'multiformats/cid'
9
-
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
4
+
import express from "express";
5
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
6
+
import { lexicons } from "../../../../lexicons";
7
+
import { isObj, hasProp } from "../../../../util";
8
+
import { CID } from "multiformats/cid";
9
+
import { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server";
10
10
11
11
export interface QueryParams {
12
-
/** The handle or DID of the repo. */
13
-
repo: string
14
-
/** The NSID of the record collection. */
15
-
collection: string
16
-
/** The Record Key. */
17
-
rkey: string
18
-
/** The CID of the version of the record. If not specified, then return the most recent version. */
19
-
cid?: string
12
+
/** The handle or DID of the repo. */
13
+
repo: string;
14
+
/** The NSID of the record collection. */
15
+
collection: string;
16
+
/** The Record Key. */
17
+
rkey: string;
18
+
/** The CID of the version of the record. If not specified, then return the most recent version. */
19
+
cid?: string;
20
20
}
21
21
22
-
export type InputSchema = undefined
22
+
export type InputSchema = undefined;
23
23
24
24
export interface OutputSchema {
25
-
uri: string
26
-
cid?: string
27
-
value: {}
28
-
[k: string]: unknown
25
+
uri: string;
26
+
cid?: string;
27
+
value: {};
28
+
[k: string]: unknown;
29
29
}
30
30
31
-
export type HandlerInput = undefined
31
+
export type HandlerInput = undefined;
32
32
33
33
export interface HandlerSuccess {
34
-
encoding: 'application/json'
35
-
body: OutputSchema
36
-
headers?: { [key: string]: string }
34
+
encoding: "application/json";
35
+
body: OutputSchema;
36
+
headers?: { [key: string]: string };
37
37
}
38
38
39
39
export interface HandlerError {
40
-
status: number
41
-
message?: string
42
-
error?: 'RecordNotFound'
40
+
status: number;
41
+
message?: string;
42
+
error?: "RecordNotFound";
43
43
}
44
44
45
-
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
45
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
46
46
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
47
-
auth: HA
48
-
params: QueryParams
49
-
input: HandlerInput
50
-
req: express.Request
51
-
res: express.Response
52
-
}
47
+
auth: HA;
48
+
params: QueryParams;
49
+
input: HandlerInput;
50
+
req: express.Request;
51
+
res: express.Response;
52
+
};
53
53
export type Handler<HA extends HandlerAuth = never> = (
54
-
ctx: HandlerReqCtx<HA>,
55
-
) => Promise<HandlerOutput> | HandlerOutput
54
+
ctx: HandlerReqCtx<HA>,
55
+
) => Promise<HandlerOutput> | HandlerOutput;
+48
-48
src/lexicons/types/com/atproto/repo/listRecords.ts
+48
-48
src/lexicons/types/com/atproto/repo/listRecords.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import express from 'express'
5
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
-
import { lexicons } from '../../../../lexicons'
7
-
import { isObj, hasProp } from '../../../../util'
8
-
import { CID } from 'multiformats/cid'
9
-
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
4
+
import express from "express";
5
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
6
+
import { lexicons } from "../../../../lexicons";
7
+
import { isObj, hasProp } from "../../../../util";
8
+
import { CID } from "multiformats/cid";
9
+
import { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server";
10
10
11
11
export interface QueryParams {
12
-
/** The handle or DID of the repo. */
13
-
repo: string
14
-
/** The NSID of the record type. */
15
-
collection: string
16
-
/** The number of records to return. */
17
-
limit: number
18
-
cursor?: string
19
-
/** DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) */
20
-
rkeyStart?: string
21
-
/** DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) */
22
-
rkeyEnd?: string
23
-
/** Flag to reverse the order of the returned records. */
24
-
reverse?: boolean
12
+
/** The handle or DID of the repo. */
13
+
repo: string;
14
+
/** The NSID of the record type. */
15
+
collection: string;
16
+
/** The number of records to return. */
17
+
limit: number;
18
+
cursor?: string;
19
+
/** DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) */
20
+
rkeyStart?: string;
21
+
/** DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) */
22
+
rkeyEnd?: string;
23
+
/** Flag to reverse the order of the returned records. */
24
+
reverse?: boolean;
25
25
}
26
26
27
-
export type InputSchema = undefined
27
+
export type InputSchema = undefined;
28
28
29
29
export interface OutputSchema {
30
-
cursor?: string
31
-
records: Record[]
32
-
[k: string]: unknown
30
+
cursor?: string;
31
+
records: Record[];
32
+
[k: string]: unknown;
33
33
}
34
34
35
-
export type HandlerInput = undefined
35
+
export type HandlerInput = undefined;
36
36
37
37
export interface HandlerSuccess {
38
-
encoding: 'application/json'
39
-
body: OutputSchema
40
-
headers?: { [key: string]: string }
38
+
encoding: "application/json";
39
+
body: OutputSchema;
40
+
headers?: { [key: string]: string };
41
41
}
42
42
43
43
export interface HandlerError {
44
-
status: number
45
-
message?: string
44
+
status: number;
45
+
message?: string;
46
46
}
47
47
48
-
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
48
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
49
49
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
50
-
auth: HA
51
-
params: QueryParams
52
-
input: HandlerInput
53
-
req: express.Request
54
-
res: express.Response
55
-
}
50
+
auth: HA;
51
+
params: QueryParams;
52
+
input: HandlerInput;
53
+
req: express.Request;
54
+
res: express.Response;
55
+
};
56
56
export type Handler<HA extends HandlerAuth = never> = (
57
-
ctx: HandlerReqCtx<HA>,
58
-
) => Promise<HandlerOutput> | HandlerOutput
57
+
ctx: HandlerReqCtx<HA>,
58
+
) => Promise<HandlerOutput> | HandlerOutput;
59
59
60
60
export interface Record {
61
-
uri: string
62
-
cid: string
63
-
value: {}
64
-
[k: string]: unknown
61
+
uri: string;
62
+
cid: string;
63
+
value: {};
64
+
[k: string]: unknown;
65
65
}
66
66
67
67
export function isRecord(v: unknown): v is Record {
68
-
return (
69
-
isObj(v) &&
70
-
hasProp(v, '$type') &&
71
-
v.$type === 'com.atproto.repo.listRecords#record'
72
-
)
68
+
return (
69
+
isObj(v) &&
70
+
hasProp(v, "$type") &&
71
+
v.$type === "com.atproto.repo.listRecords#record"
72
+
);
73
73
}
74
74
75
75
export function validateRecord(v: unknown): ValidationResult {
76
-
return lexicons.validate('com.atproto.repo.listRecords#record', v)
76
+
return lexicons.validate("com.atproto.repo.listRecords#record", v);
77
77
}
+14
-14
src/lexicons/types/com/atproto/repo/strongRef.ts
+14
-14
src/lexicons/types/com/atproto/repo/strongRef.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../../lexicons'
6
-
import { isObj, hasProp } from '../../../../util'
7
-
import { CID } from 'multiformats/cid'
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../../lexicons";
6
+
import { isObj, hasProp } from "../../../../util";
7
+
import { CID } from "multiformats/cid";
8
8
9
9
export interface Main {
10
-
uri: string
11
-
cid: string
12
-
[k: string]: unknown
10
+
uri: string;
11
+
cid: string;
12
+
[k: string]: unknown;
13
13
}
14
14
15
15
export function isMain(v: unknown): v is Main {
16
-
return (
17
-
isObj(v) &&
18
-
hasProp(v, '$type') &&
19
-
(v.$type === 'com.atproto.repo.strongRef#main' ||
20
-
v.$type === 'com.atproto.repo.strongRef')
21
-
)
16
+
return (
17
+
isObj(v) &&
18
+
hasProp(v, "$type") &&
19
+
(v.$type === "com.atproto.repo.strongRef#main" ||
20
+
v.$type === "com.atproto.repo.strongRef")
21
+
);
22
22
}
23
23
24
24
export function validateMain(v: unknown): ValidationResult {
25
-
return lexicons.validate('com.atproto.repo.strongRef#main', v)
25
+
return lexicons.validate("com.atproto.repo.strongRef#main", v);
26
26
}
+29
src/lexicons/types/li/plonk/comment.ts
+29
src/lexicons/types/li/plonk/comment.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../lexicons";
6
+
import { isObj, hasProp } from "../../../util";
7
+
import { CID } from "multiformats/cid";
8
+
import * as ComAtprotoRepoStrongRef from "../../com/atproto/repo/strongRef";
9
+
10
+
export interface Record {
11
+
/** comment body */
12
+
content: string;
13
+
/** comment creation timestamp */
14
+
createdAt: string;
15
+
post: ComAtprotoRepoStrongRef.Main;
16
+
[k: string]: unknown;
17
+
}
18
+
19
+
export function isRecord(v: unknown): v is Record {
20
+
return (
21
+
isObj(v) &&
22
+
hasProp(v, "$type") &&
23
+
(v.$type === "li.plonk.comment#main" || v.$type === "li.plonk.comment")
24
+
);
25
+
}
26
+
27
+
export function validateRecord(v: unknown): ValidationResult {
28
+
return lexicons.validate("li.plonk.comment#main", v);
29
+
}
+28
src/lexicons/types/li/plonk/paste.ts
+28
src/lexicons/types/li/plonk/paste.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../lexicons";
6
+
import { isObj, hasProp } from "../../../util";
7
+
import { CID } from "multiformats/cid";
8
+
9
+
export interface Record {
10
+
code: string;
11
+
shortUrl: string;
12
+
lang: string;
13
+
title: string;
14
+
createdAt: string;
15
+
[k: string]: unknown;
16
+
}
17
+
18
+
export function isRecord(v: unknown): v is Record {
19
+
return (
20
+
isObj(v) &&
21
+
hasProp(v, "$type") &&
22
+
(v.$type === "li.plonk.paste#main" || v.$type === "li.plonk.paste")
23
+
);
24
+
}
25
+
26
+
export function validateRecord(v: unknown): ValidationResult {
27
+
return lexicons.validate("li.plonk.paste#main", v);
28
+
}
-29
src/lexicons/types/ovh/plonk/comment.ts
-29
src/lexicons/types/ovh/plonk/comment.ts
···
1
-
/**
2
-
* GENERATED CODE - DO NOT MODIFY
3
-
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../lexicons'
6
-
import { isObj, hasProp } from '../../../util'
7
-
import { CID } from 'multiformats/cid'
8
-
import * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef'
9
-
10
-
export interface Record {
11
-
/** comment body */
12
-
content: string
13
-
/** comment creation timestamp */
14
-
createdAt: string
15
-
post: ComAtprotoRepoStrongRef.Main
16
-
[k: string]: unknown
17
-
}
18
-
19
-
export function isRecord(v: unknown): v is Record {
20
-
return (
21
-
isObj(v) &&
22
-
hasProp(v, '$type') &&
23
-
(v.$type === 'ovh.plonk.comment#main' || v.$type === 'ovh.plonk.comment')
24
-
)
25
-
}
26
-
27
-
export function validateRecord(v: unknown): ValidationResult {
28
-
return lexicons.validate('ovh.plonk.comment#main', v)
29
-
}
-28
src/lexicons/types/ovh/plonk/paste.ts
-28
src/lexicons/types/ovh/plonk/paste.ts
···
1
-
/**
2
-
* GENERATED CODE - DO NOT MODIFY
3
-
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../lexicons'
6
-
import { isObj, hasProp } from '../../../util'
7
-
import { CID } from 'multiformats/cid'
8
-
9
-
export interface Record {
10
-
code: string
11
-
shortUrl: string
12
-
lang: string
13
-
title: string
14
-
createdAt: string
15
-
[k: string]: unknown
16
-
}
17
-
18
-
export function isRecord(v: unknown): v is Record {
19
-
return (
20
-
isObj(v) &&
21
-
hasProp(v, '$type') &&
22
-
(v.$type === 'ovh.plonk.paste#main' || v.$type === 'ovh.plonk.paste')
23
-
)
24
-
}
25
-
26
-
export function validateRecord(v: unknown): ValidationResult {
27
-
return lexicons.validate('ovh.plonk.paste#main', v)
28
-
}
+4
-4
src/lexicons/util.ts
+4
-4
src/lexicons/util.ts
···
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
export function isObj(v: unknown): v is Record<string, unknown> {
5
-
return typeof v === 'object' && v !== null
5
+
return typeof v === "object" && v !== null;
6
6
}
7
7
8
8
export function hasProp<K extends PropertyKey>(
9
-
data: object,
10
-
prop: K,
9
+
data: object,
10
+
prop: K,
11
11
): data is Record<K, unknown> {
12
-
return prop in data
12
+
return prop in data;
13
13
}
+6
-6
src/lib.ts
+6
-6
src/lib.ts
···
4
4
dotenv.config();
5
5
6
6
export const env = cleanEnv(process.env, {
7
-
NODE_ENV: str({
7
+
PLONK_NODE_ENV: str({
8
8
devDefault: testOnly("test"),
9
9
choices: ["development", "production", "test"],
10
10
}),
11
-
HOST: host({ devDefault: testOnly("localhost") }),
12
-
PORT: port({ devDefault: testOnly(3000) }),
13
-
PUBLIC_URL: str({}),
14
-
DB_PATH: str({ devDefault: ":memory:" }),
15
-
COOKIE_SECRET: str({ devDefault: "00000000000000000000000000000001" }),
11
+
PLONK_HOST: host({ devDefault: testOnly("localhost") }),
12
+
PLONK_PORT: port({ devDefault: testOnly(3000) }),
13
+
PLONK_PUBLIC_URL: str({}),
14
+
PLONK_DB_PATH: str({ devDefault: ":memory:" }),
15
+
PLONK_COOKIE_SECRET: str({ devDefault: "00000000000000000000000000000000" }),
16
16
});
+1
-1
src/mixins/head.pug
+1
-1
src/mixins/head.pug
···
2
2
head
3
3
meta(name="viewport" content="width=device-width, initial-scale=1.0")
4
4
meta(charset='UTF-8')
5
-
title #{title}
5
+
title #{title} · plonk.li
6
6
link(rel="stylesheet", href="/styles.css")
7
7
link(rel="preconnect" href="https://rsms.me/")
8
8
link(rel="stylesheet" href="https://rsms.me/inter/inter.css")
+15
src/mixins/header.pug
+15
src/mixins/header.pug
···
1
+
mixin header(ownDid, didHandleMap)
2
+
div.header
3
+
div.left-side
4
+
div.right-side
5
+
p
6
+
a(href="/") home
7
+
| ·
8
+
if ownDid
9
+
a(href=`/u/${encodeURIComponent(ownDid)}`) my plonks
10
+
| ·
11
+
a(href="/logout") logout
12
+
else
13
+
a(href="/login") login
14
+
| to get plonkin'
15
+
+3
-2
src/mixins/post.pug
+3
-2
src/mixins/post.pug
···
1
1
mixin post(paste, handle, did)
2
2
div.post
3
3
p
4
-
a(href=`/p/${paste.shortUrl}`)
4
+
a(href=`/p/${paste.shortUrl}`).post-link
5
5
| #{paste.title}
6
6
p.post-info
7
7
| by
···
13
13
| #{paste.lang}
14
14
| ·
15
15
| #{paste.code.split('\n').length} loc
16
-
16
+
| ·
17
+
a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')}
+4
src/mixins/utils.pug
+4
src/mixins/utils.pug
+89
-5
src/public/styles.css
+89
-5
src/public/styles.css
···
1
-
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
1
+
@font-face {
2
+
font-family: 'NerdIosevka';
3
+
src: url('../assets/NerdIosevka-Regular.woff2') format('woff2');
4
+
font-weight: normal;
5
+
font-style: monospace;
6
+
}
2
7
3
8
:root {
4
9
/* Light mode colors */
···
28
33
}
29
34
30
35
* {
31
-
font-family: 'IBM Plex Mono', monospace;
36
+
font-family: 'NerdIosevka', monospace;
37
+
font-size: 0.9rem;
32
38
}
33
39
34
40
body {
···
52
58
}
53
59
54
60
pre {
55
-
background-color: var(--bg-color-muted);
56
61
padding: 1rem;
57
62
overflow-x: auto;
58
63
}
59
64
65
+
.comment-body {
66
+
background-color: var(--bg-color);
67
+
padding: 0;
68
+
margin-top: 0.1rem;
69
+
}
70
+
71
+
.comment-info {
72
+
margin-bottom: 0;
73
+
}
74
+
60
75
input, textarea, select, button {
61
76
border: none;
62
77
padding: 1rem;
···
93
108
hr {
94
109
border: none;
95
110
border-top: 1px solid var(--bg-color-muted);
96
-
padding: 1rem;
97
111
}
98
112
99
113
.post-form {
···
127
141
align-self: flex-end;
128
142
}
129
143
144
+
.post-link {
145
+
color: var(--text-color);
146
+
text-decoration: none;
147
+
}
148
+
.post-link:hover {
149
+
text-decoration: underline;
150
+
}
151
+
.post-link:visited {
152
+
color: var(--text-color-muted);
153
+
}
154
+
155
+
.post-info {
156
+
margin-top: 0;
157
+
}
158
+
159
+
.post-info, .comment-info {
160
+
color: var(--text-color-muted);
161
+
}
162
+
.post-info a, .comment-info a {
163
+
color: var(--text-color-muted);
164
+
text-decoration: none;
165
+
}
166
+
.post-info a:visited, .comment-info a:visited {
167
+
color: var(--text-color-muted);
168
+
}
169
+
.post-info a:hover, .comment-info a:hover {
170
+
text-decoration: underline;
171
+
}
172
+
130
173
.timeline, .comments {
131
174
display: flex;
132
175
flex-direction: column;
133
176
gap: 1rem;
177
+
padding-bottom: 1rem;
134
178
}
135
179
136
180
.login-input-title {
···
141
185
flex: 1
142
186
}
143
187
144
-
.header {
188
+
.header, .footer {
145
189
display: flex;
146
190
flex-direction: row;
147
191
justify-content: space-between;
148
192
align-items: center;
149
193
}
194
+
195
+
select {
196
+
-webkit-appearance: none;
197
+
-moz-appearance: none;
198
+
text-indent: 1px;
199
+
text-overflow: '';
200
+
}
201
+
202
+
.code-line {
203
+
display: flex;
204
+
}
205
+
206
+
.code-line-num {
207
+
white-space: pre;
208
+
-webkit-user-select: none;
209
+
user-select: none;
210
+
margin-right: 0.4em;
211
+
padding: 0 0.4em 0 0.4em;
212
+
color: var(--text-color-muted);
213
+
text-align: right;
214
+
}
215
+
216
+
.code-line-content {
217
+
color: var(--text-color);
218
+
}
219
+
220
+
.header, .footer {
221
+
color: var(--text-color);
222
+
}
223
+
224
+
.header a, .header a:visited,
225
+
.footer a, .footer a:visited {
226
+
color: var(--link-color);
227
+
text-decoration: none;
228
+
}
229
+
230
+
.header a:hover,
231
+
.footer a:hover {
232
+
text-decoration: underline;
233
+
}
+148
-101
src/routes.ts
+148
-101
src/routes.ts
···
6
6
import { isValidHandle, AtUri } from "@atproto/syntax";
7
7
import { IncomingMessage, ServerResponse } from "node:http";
8
8
import { Agent } from "@atproto/api";
9
-
import { getPds, DidResolver } from "@atproto/identity";
10
9
import { TID } from "@atproto/common";
11
-
import { Agent } from "@atproto/api";
12
10
import { newShortUrl } from "#/db";
13
11
14
-
import * as Paste from "#/lexicons/types/ovh/plonk/paste";
15
-
import * as Comment from "#/lexicons/types/ovh/plonk/comment";
16
-
import { ComAtprotoRepoNS } from "#/lexicons";
12
+
import * as Paste from "#/lexicons/types/li/plonk/paste";
13
+
import * as Comment from "#/lexicons/types/li/plonk/comment";
17
14
18
15
type Session = {
19
16
did: string;
20
17
};
21
18
22
-
async function getSessionAgent(
19
+
async function getSession(
23
20
req: IncomingMessage,
24
21
res: ServerResponse<IncomingMessage>,
25
-
ctx: Ctx,
26
22
) {
27
-
const session = await getIronSession<Session>(req, res, {
23
+
return await getIronSession<Session>(req, res, {
28
24
cookieName: "plonk-id",
29
-
password: env.COOKIE_SECRET,
25
+
password: env.PLONK_COOKIE_SECRET,
26
+
cookieOptions: {
27
+
secure: env.PLONK_NODE_ENV === "production",
28
+
},
30
29
});
30
+
}
31
+
32
+
async function getSessionAgent(
33
+
req: IncomingMessage,
34
+
res: ServerResponse<IncomingMessage>,
35
+
ctx: Ctx,
36
+
) {
37
+
const session = await getSession(req, res);
31
38
if (!session.did) return null;
32
39
try {
33
40
const oauthSession = await ctx.oauthClient.restore(session.did);
···
42
49
export const createRouter = (ctx: Ctx) => {
43
50
const router = express.Router();
44
51
45
-
// Static assets
46
-
router.use(
47
-
"/public",
48
-
express.static(path.join(__dirname, "pages", "public")),
49
-
);
50
-
52
+
router.use("/assets", express.static(path.join(__dirname, "assets")));
51
53
// OAuth metadata
52
54
router.get("/client-metadata.json", async (_req, res) => {
53
55
return res.json(ctx.oauthClient.clientMetadata);
···
57
59
const params = new URLSearchParams(req.originalUrl.split("?")[1]);
58
60
try {
59
61
const { session } = await ctx.oauthClient.callback(params);
60
-
const clientSession = await getIronSession<Session>(req, res, {
61
-
cookieName: "plonk-id",
62
-
password: env.COOKIE_SECRET,
63
-
});
64
-
ctx.logger.info(clientSession.did, "client session did");
62
+
const clientSession = await getSession(req, res);
65
63
//assert(!clientSession.did, "session already exists");
66
64
clientSession.did = session.did;
67
65
await clientSession.save();
···
98
96
});
99
97
100
98
router.get("/logout", async (req, res) => {
101
-
const session = await getIronSession<Session>(req, res, {
102
-
cookieName: "plonk-id",
103
-
password: env.COOKIE_SECRET,
104
-
});
99
+
const session = await getSession(req, res);
105
100
session.destroy();
106
101
return res.redirect("/");
107
102
});
···
110
105
const agent = await getSessionAgent(req, res, ctx);
111
106
const pastes = await ctx.db
112
107
.selectFrom("paste")
113
-
.selectAll()
114
-
.orderBy("indexedAt", "desc")
108
+
.leftJoin("comment", "comment.pasteUri", "paste.uri")
109
+
.select([
110
+
"paste.uri",
111
+
"paste.shortUrl",
112
+
"paste.authorDid",
113
+
"paste.code",
114
+
"paste.lang",
115
+
"paste.title",
116
+
"paste.createdAt",
117
+
"paste.indexedAt as pasteIndexedAt",
118
+
ctx.db.fn.count("comment.uri").as("commentCount")
119
+
])
120
+
.groupBy("paste.uri")
121
+
.orderBy("pasteIndexedAt", "desc")
115
122
.limit(25)
116
123
.execute();
117
124
118
125
// Map user DIDs to their domain-name handles
119
126
const didHandleMap = await ctx.resolver.resolveDidsToHandles(
120
-
pastes.map((s) => s.authorDid).concat(agent? [agent.assertDid]:[]),
127
+
pastes.map((s) => s.authorDid).concat(agent ? [agent.assertDid] : []),
121
128
);
122
129
123
130
if (!agent) {
···
133
140
134
141
router.get("/u/:authorDid", async (req, res) => {
135
142
const { authorDid } = req.params;
136
-
const resolver = new DidResolver({});
137
-
const didDocument = await resolver.resolve(authorDid);
138
-
if (!didDocument) {
139
-
return res.status(404);
140
-
}
141
-
const pds = getPds(didDocument);
142
-
if (!pds) {
143
-
return res.status(404);
144
-
}
145
-
const agent = new Agent(pds);
146
-
const response = await agent.com.atproto.repo.listRecords({
147
-
repo: authorDid,
148
-
collection: 'ovh.plonk.paste',
149
-
limit: 99,
150
-
});
151
-
const pastes = response.data.records;
152
-
let didHandleMap = {};
143
+
const pastes = await ctx.db
144
+
.selectFrom("paste")
145
+
.leftJoin("comment", "comment.pasteUri", "paste.uri")
146
+
.select([
147
+
"paste.uri",
148
+
"paste.shortUrl",
149
+
"paste.authorDid as pasteAuthorDid",
150
+
"paste.code",
151
+
"paste.lang",
152
+
"paste.title",
153
+
"paste.createdAt as pasteCreatedAt",
154
+
"paste.indexedAt as pasteIndexedAt",
155
+
ctx.db.fn.count("comment.uri").as("commentCount")
156
+
])
157
+
.groupBy("paste.uri")
158
+
.where("pasteAuthorDid", "=", authorDid)
159
+
.orderBy("pasteCreatedAt", "desc")
160
+
.execute();
161
+
let didHandleMap: Record<string, string> = {};
153
162
didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid);
154
-
return res.render("user", { pastes, authorDid, didHandleMap });
163
+
const ownAgent = await getSessionAgent(req, res, ctx);
164
+
if (!ownAgent) {
165
+
return res.render("user", { pastes, authorDid, didHandleMap });
166
+
} else {
167
+
const ownDid = ownAgent.assertDid;
168
+
didHandleMap[ownDid] = await ctx.resolver.resolveDidToHandle(ownDid);
169
+
return res.render("user", { pastes, authorDid, ownDid, didHandleMap });
170
+
}
155
171
});
156
172
157
173
router.get("/p/:shortUrl", async (req, res) => {
158
174
const { shortUrl } = req.params;
159
175
const ret = await ctx.db
160
176
.selectFrom("paste")
177
+
.leftJoin("comment", "comment.pasteUri", "paste.uri")
178
+
.select([
179
+
"paste.uri as pasteUri",
180
+
"comment.pasteCid as pasteCid",
181
+
"paste.authorDid as pasteAuthorDid",
182
+
"paste.code as pasteCode",
183
+
"paste.lang as pasteLang",
184
+
"paste.title as pasteTitle",
185
+
"paste.createdAt as pasteCreatedAt",
186
+
"comment.uri as commentUri",
187
+
"comment.authorDid as commentAuthorDid",
188
+
"comment.body as commentBody",
189
+
"comment.createdAt as commentCreatedAt",
190
+
])
161
191
.where("shortUrl", "=", shortUrl)
162
-
.select(["authorDid", "uri"])
163
-
.executeTakeFirst();
164
-
if (!ret) {
165
-
return res.status(404);
166
-
}
167
-
var comments = await ctx.db
168
-
.selectFrom("comment")
169
-
.selectAll()
170
-
.where("pasteUri", '=', ret.uri)
171
192
.execute();
172
-
const { authorDid: did, uri } = ret;
173
-
const didHandleMap = await ctx.resolver.resolveDidsToHandles(
174
-
comments.map((c) => c.authorDid).concat([did]),
175
-
)
176
-
const resolver = new DidResolver({});
177
-
const didDocument = await resolver.resolve(did);
178
-
if (!didDocument) {
193
+
if (ret.length === 0) {
179
194
return res.status(404);
180
195
}
181
-
const pds = getPds(didDocument);
182
-
if (!pds) {
183
-
return res.status(404);
184
-
}
185
-
const agent = new Agent(pds);
186
-
const aturi = new AtUri(uri);
187
-
const response = await agent.com.atproto.repo.getRecord({
188
-
repo: aturi.hostname,
189
-
collection: aturi.collection,
190
-
rkey: aturi.rkey
191
-
});
196
+
const {
197
+
pasteAuthorDid,
198
+
pasteUri,
199
+
pasteCode,
200
+
pasteLang,
201
+
pasteTitle,
202
+
pasteCreatedAt,
203
+
} = ret[0];
204
+
let didHandleMap = await ctx.resolver.resolveDidsToHandles(
205
+
[ret[0].pasteAuthorDid].concat(
206
+
ret.flatMap((row) =>
207
+
row.commentAuthorDid ? [row.commentAuthorDid] : [],
208
+
),
209
+
),
210
+
);
192
211
193
-
const paste =
194
-
Paste.isRecord(response.data.value) &&
195
-
Paste.validateRecord(response.data.value).success
196
-
? response.data.value
197
-
: {};
212
+
const paste = {
213
+
uri: pasteUri,
214
+
code: pasteCode,
215
+
title: pasteTitle,
216
+
lang: pasteLang,
217
+
shortUrl,
218
+
createdAt: pasteCreatedAt,
219
+
authorDid: pasteAuthorDid,
220
+
};
198
221
199
-
return res.render("paste", { paste, authorDid: did, uri: response.data.uri, didHandleMap, shortUrl, comments });
222
+
const comments = ret
223
+
.filter((row) => row.commentUri)
224
+
.map((row) => {
225
+
return {
226
+
uri: row.commentUri,
227
+
authorDid: row.commentAuthorDid,
228
+
body: row.commentBody,
229
+
createdAt: row.commentCreatedAt,
230
+
};
231
+
});
232
+
233
+
const ownAgent = await getSessionAgent(req, res, ctx);
234
+
if (!ownAgent) {
235
+
return res.render("paste", {
236
+
paste,
237
+
didHandleMap,
238
+
comments,
239
+
});
240
+
} else {
241
+
const ownDid = ownAgent.assertDid;
242
+
didHandleMap[ownDid] = await ctx.resolver.resolveDidToHandle(ownDid);
243
+
return res.render("paste", {
244
+
paste,
245
+
ownDid,
246
+
didHandleMap,
247
+
comments,
248
+
});
249
+
}
200
250
});
201
251
202
252
router.get("/p/:shortUrl/raw", async (req, res) => {
203
-
res.redirect(`/r/${req.params.shortUrl}`)
253
+
res.redirect(`/r/${req.params.shortUrl}`);
204
254
});
205
255
router.get("/r/:shortUrl", async (req, res) => {
206
256
const { shortUrl } = req.params;
···
220
270
router.get("/reset", async (req, res) => {
221
271
const agent = await getSessionAgent(req, res, ctx);
222
272
if (!agent) {
223
-
return res.redirect('/');
273
+
return res.redirect("/");
224
274
}
225
275
const response = await agent.com.atproto.repo.listRecords({
226
276
repo: agent.assertDid,
227
-
collection: 'ovh.plonk.paste',
277
+
collection: "li.plonk.paste",
228
278
limit: 10,
229
279
});
230
280
const vals = response.data.records;
···
236
286
rkey: aturl.rkey,
237
287
});
238
288
}
239
-
return res.redirect('/');
289
+
return res.redirect("/");
240
290
});
241
291
242
292
router.post("/paste", async (req, res) => {
···
251
301
const rkey = TID.nextStr();
252
302
const shortUrl = await newShortUrl(ctx.db);
253
303
const record = {
254
-
$type: "ovh.plonk.paste",
304
+
$type: "li.plonk.paste",
255
305
code: req.body?.code,
256
306
lang: req.body?.lang,
257
307
shortUrl,
···
270
320
try {
271
321
const res = await agent.com.atproto.repo.putRecord({
272
322
repo: agent.assertDid,
273
-
collection: "ovh.plonk.paste",
323
+
collection: "li.plonk.paste",
274
324
rkey,
275
325
record,
276
326
validate: false,
···
299
349
indexedAt: new Date().toISOString(),
300
350
})
301
351
.execute();
302
-
ctx.logger.info(res, "wrote back to db");
303
352
return res.redirect(`/p/${shortUrl}`);
304
353
} catch (err) {
305
354
ctx.logger.warn(
···
320
369
.type("html")
321
370
.send("<h1>Error: Session required</h1>");
322
371
}
323
-
372
+
324
373
const pasteUri = req.params.paste;
325
374
const aturi = new AtUri(pasteUri);
326
375
const pasteResponse = await agent.com.atproto.repo.getRecord({
327
376
repo: aturi.hostname,
328
377
collection: aturi.collection,
329
-
rkey: aturi.rkey
378
+
rkey: aturi.rkey,
330
379
});
331
380
const pasteCid = pasteResponse.data.cid;
332
381
if (!pasteCid) {
333
-
return res
334
-
.status(401)
335
-
.type("html")
336
-
.send("invalid paste");
382
+
return res.status(401).type("html").send("invalid paste");
337
383
}
338
384
339
385
const rkey = TID.nextStr();
340
386
const record = {
341
-
$type: "ovh.plonk.comment",
387
+
$type: "li.plonk.comment",
342
388
content: req.body?.comment,
343
389
post: {
344
390
uri: pasteUri,
345
-
cid: pasteCid
391
+
cid: pasteCid,
346
392
},
347
393
createdAt: new Date().toISOString(),
348
394
};
···
358
404
try {
359
405
const res = await agent.com.atproto.repo.putRecord({
360
406
repo: agent.assertDid,
361
-
collection: "ovh.plonk.comment",
407
+
collection: "li.plonk.comment",
362
408
rkey,
363
409
record,
364
410
validate: false,
···
385
431
indexedAt: new Date().toISOString(),
386
432
})
387
433
.execute();
388
-
ctx.logger.info(res, "wrote back to db");
389
-
const originalPaste = await ctx.db.selectFrom('paste').selectAll().where('uri', '=', pasteUri).executeTakeFirst();
390
-
return res.redirect(`/p/${originalPaste.shortUrl}#${encodeURIComponent(uri)}`);
434
+
const originalPaste = await ctx.db
435
+
.selectFrom("paste")
436
+
.selectAll()
437
+
.where("uri", "=", pasteUri)
438
+
.executeTakeFirst();
439
+
return res.redirect(
440
+
`/p/${originalPaste.shortUrl}#${encodeURIComponent(uri)}`,
441
+
);
391
442
} catch (err) {
392
443
ctx.logger.warn(
393
444
{ err },
···
400
451
401
452
return router;
402
453
};
403
-
404
-
// https://pds.icyphox.sh/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3A3ft67n4xnawzq4qi7mcksxj5
405
-
// at://did:plc:3ft67n4xnawzq4qi7mcksxj5/ovh.plonk.paste/3lcs3lnslbk2d
406
-
// https://pds.icyphox.sh/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3A3ft67n4xnawzq4qi7mcksxj5&collection=ovh.plonk.paste&rkey=3lcqt7newvc2c
+6
-14
src/views/index.pug
+6
-14
src/views/index.pug
···
1
1
include ../mixins/mkPost
2
2
include ../mixins/head
3
+
include ../mixins/header
4
+
include ../mixins/footer
3
5
include ../mixins/utils
4
6
include ../mixins/post
5
7
···
18
20
"c",
19
21
"c#",
20
22
"c++",
23
+
"cobol",
21
24
].toSorted())
22
25
doctype html
23
26
html
24
27
+head("timeline")
25
28
body
26
29
main#content
27
-
div.header
28
-
div.left-side
29
-
if ownDid
30
-
| logged in as @#{didHandleMap[ownDid]}
31
-
div.right-side
32
-
if ownDid
33
-
p
34
-
a(href=`/u/${encodeURIComponent(ownDid)}`) my plonks
35
-
| ·
36
-
a(href="/logout") logout
37
-
else
38
-
p
39
-
a(href="/login") login
40
-
| to get plonkin'
30
+
+header(ownDid, didHandleMap)
41
31
42
32
if ownDid
43
33
+mkPost()
···
46
36
each paste in pastes
47
37
- var handle = didHandleMap[paste.authorDid]
48
38
+post(paste, handle, paste.authorDid)
39
+
40
+
+footer()
+5
-8
src/views/login.pug
+5
-8
src/views/login.pug
···
1
+
include ../mixins/head
2
+
include ../mixins/footer
3
+
1
4
doctype html
2
5
html
3
-
head
4
-
meta(name="viewport" content="width=device-width, initial-scale=1.0")
5
-
meta(charset='UTF-8')
6
-
title login
7
-
link(rel="stylesheet", href="/styles.css")
8
-
link(rel="preconnect" href="https://rsms.me/")
9
-
link(rel="stylesheet" href="https://rsms.me/inter/inter.css")
10
-
script(src="https://cdn.dashjs.org/latest/dash.all.min.js")
6
+
+head("login")
11
7
body
12
8
main#content
13
9
h1 login
···
15
11
div.login-row
16
12
input(type="text" name="handle" placeholder="enter handle" required).login-input-title
17
13
button(type="submit").login-submit-button login
14
+
+footer()
+40
-19
src/views/paste.pug
+40
-19
src/views/paste.pug
···
1
1
- var now = new Date()
2
2
include ../mixins/head
3
+
include ../mixins/header
4
+
include ../mixins/footer
3
5
include ../mixins/utils
4
6
doctype html
5
7
html
6
-
+head(paste.title)
8
+
+head(`${paste.title} · ${didHandleMap[paste.authorDid]}`)
7
9
body
8
10
main#content
11
+
+header(ownDid, didHandleMap)
9
12
h1 #{paste.title}
10
-
p
11
-
| by @#{didHandleMap[authorDid]} ·
13
+
p.post-info
14
+
| @#{didHandleMap[paste.authorDid]} ·
12
15
| #{timeDifference(now, Date.parse(paste.createdAt))} ago ·
13
16
| #{paste.lang} ·
14
17
| #{paste.code.split('\n').length} loc ·
15
-
a(href=`/r/${shortUrl}`) raw
18
+
a(href=`/r/${paste.shortUrl}`) raw
19
+
| ·
20
+
| #{comments.length} #{pluralize(comments.length, 'comment')}
16
21
pre
17
-
| #{paste.code}
22
+
code
23
+
- var lines = paste.code.split(/\r?\n|\r|\n/g)
24
+
- var tot_chars = lines.length.toString().length
25
+
each line, idx in lines
26
+
span.code-line
27
+
span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`)
28
+
| #{idx + 1}
29
+
span.code-line-content #{line}
18
30
hr
19
31
20
-
div.comments
21
-
each comment in comments
22
-
div.comment(id=`${encodeURIComponent(comment.uri)}`)
23
-
p
24
-
| by @#{didHandleMap[comment.authorDid]} ·
25
-
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
26
-
p
27
-
| #{comment.body}
28
-
hr
32
+
if comments.length != 0
33
+
h1(id="comments") comments
34
+
div.comments
35
+
each comment in comments
36
+
div.comment(id=`${encodeURIComponent(comment.uri)}`)
37
+
p.comment-info
38
+
a(href=`/u/${comment.authorDid}`)
39
+
| @#{didHandleMap[comment.authorDid]}
40
+
| ·
41
+
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
42
+
pre.comment-body #{comment.body}
43
+
44
+
if ownDid
45
+
form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
46
+
div.post-row
47
+
textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code
29
48
30
-
form(action=`/${encodeURIComponent(uri)}/comment` method="post").post-form
31
-
div.post-row
32
-
textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code
49
+
div.post-submit-row
50
+
button(type="submit").post-input-submit zonk!
51
+
else
52
+
p
53
+
a(href="/login") login
54
+
| to post a comment
33
55
34
-
div.post-submit-row
35
-
button(type="submit").post-input-submit zonk!
56
+
+footer()
+6
-1
src/views/user.pug
+6
-1
src/views/user.pug
···
1
1
- var now = new Date()
2
2
- var handle = didHandleMap[authorDid]
3
3
include ../mixins/head
4
+
include ../mixins/header
5
+
include ../mixins/footer
4
6
include ../mixins/utils
5
7
include ../mixins/post
6
8
doctype html
···
8
10
+head(handle)
9
11
body
10
12
main#content
13
+
+header(ownDid, didHandleMap)
11
14
h1 plonks by @#{handle}
12
15
div.timeline
13
16
each paste in pastes
14
-
+post(paste.value, handle, authorDid)
17
+
+post(paste, handle, authorDid)
18
+
19
+
+footer()
-12
tsup.config.ts
-12
tsup.config.ts
+463
x
+463
x
···
1
+
From 489bb110616aa4da596aed7ae0a048c919ed333e Mon Sep 17 00:00:00 2001
2
+
From: Akshay <nerdy@peppe.rs>
3
+
Date: Thu, 26 Dec 2024 19:19:37 +0000
4
+
Subject: [PATCH 1/3] add footer, comments, linenrs etc
5
+
6
+
---
7
+
src/mixins/footer.pug | 9 +++++++++
8
+
src/mixins/post.pug | 3 ++-
9
+
src/mixins/utils.pug | 4 ++++
10
+
src/public/styles.css | 21 +++++++++++++++++++--
11
+
src/routes.ts | 33 +++++++++++++++++++++++++++++----
12
+
src/views/index.pug | 3 +++
13
+
src/views/paste.pug | 28 +++++++++++++++++++++-------
14
+
7 files changed, 87 insertions(+), 14 deletions(-)
15
+
create mode 100644 src/mixins/footer.pug
16
+
17
+
diff --git a/src/mixins/footer.pug b/src/mixins/footer.pug
18
+
new file mode 100644
19
+
index 0000000..be71086
20
+
--- /dev/null
21
+
+++ b/src/mixins/footer.pug
22
+
@@ -0,0 +1,9 @@
23
+
+mixin footer()
24
+
+ hr
25
+
+ div.footer
26
+
+ div.left-side
27
+
+ div.right-side
28
+
+ p
29
+
+ | made by
30
+
+ a(href="https://bsky.app/profile/oppi.li") @oppi.li
31
+
+
32
+
diff --git a/src/mixins/post.pug b/src/mixins/post.pug
33
+
index 77d78aa..e98bcf8 100644
34
+
--- a/src/mixins/post.pug
35
+
+++ b/src/mixins/post.pug
36
+
@@ -13,4 +13,5 @@ mixin post(paste, handle, did)
37
+
| #{paste.lang}
38
+
| ·
39
+
| #{paste.code.split('\n').length} loc
40
+
-
41
+
+ | ·
42
+
+ a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')}
43
+
diff --git a/src/mixins/utils.pug b/src/mixins/utils.pug
44
+
index 857bddd..08507d3 100644
45
+
--- a/src/mixins/utils.pug
46
+
+++ b/src/mixins/utils.pug
47
+
@@ -2,6 +2,10 @@
48
+
function randInt(min, max) {
49
+
return Math.floor(Math.random() * (max - min + 1)) + min;
50
+
}
51
+
+-
52
+
+ function pluralize(count, noun) {
53
+
+ return count==1?noun:`${noun}s`;
54
+
+ }
55
+
-
56
+
function timeDifference(current, previous) {
57
+
if (!current || !previous) {
58
+
diff --git a/src/public/styles.css b/src/public/styles.css
59
+
index f88b533..6f80f5f 100644
60
+
--- a/src/public/styles.css
61
+
+++ b/src/public/styles.css
62
+
@@ -104,7 +104,6 @@ textarea {
63
+
hr {
64
+
border: none;
65
+
border-top: 1px solid var(--bg-color-muted);
66
+
- padding: 1rem;
67
+
}
68
+
69
+
.post-form {
70
+
@@ -152,7 +151,7 @@ hr {
71
+
flex: 1
72
+
}
73
+
74
+
-.header {
75
+
+.header, .footer {
76
+
display: flex;
77
+
flex-direction: row;
78
+
justify-content: space-between;
79
+
@@ -165,3 +164,21 @@ select {
80
+
text-indent: 1px;
81
+
text-overflow: '';
82
+
}
83
+
+
84
+
+.code-line {
85
+
+ display: flex;
86
+
+}
87
+
+
88
+
+.code-line-num {
89
+
+ white-space: pre;
90
+
+ -webkit-user-select: none;
91
+
+ user-select: none;
92
+
+ margin-right: 0.4em;
93
+
+ padding: 0 0.4em 0 0.4em;
94
+
+ color: var(--text-color-muted);
95
+
+ text-align: right;
96
+
+}
97
+
+
98
+
+.code-line-content {
99
+
+ color: var(--text-color);
100
+
+}
101
+
diff --git a/src/routes.ts b/src/routes.ts
102
+
index 70f931d..17fa00e 100644
103
+
--- a/src/routes.ts
104
+
+++ b/src/routes.ts
105
+
@@ -105,8 +105,20 @@ export const createRouter = (ctx: Ctx) => {
106
+
const agent = await getSessionAgent(req, res, ctx);
107
+
const pastes = await ctx.db
108
+
.selectFrom("paste")
109
+
- .selectAll()
110
+
- .orderBy("indexedAt", "desc")
111
+
+ .leftJoin("comment", "comment.pasteUri", "paste.uri")
112
+
+ .select([
113
+
+ "paste.uri",
114
+
+ "paste.shortUrl",
115
+
+ "paste.authorDid",
116
+
+ "paste.code",
117
+
+ "paste.lang",
118
+
+ "paste.title",
119
+
+ "paste.createdAt",
120
+
+ "paste.indexedAt as pasteIndexedAt",
121
+
+ ctx.db.fn.count("comment.uri").as("commentCount")
122
+
+ ])
123
+
+ .groupBy("paste.uri")
124
+
+ .orderBy("pasteIndexedAt", "desc")
125
+
.limit(25)
126
+
.execute();
127
+
128
+
@@ -130,8 +142,21 @@ export const createRouter = (ctx: Ctx) => {
129
+
const { authorDid } = req.params;
130
+
const pastes = await ctx.db
131
+
.selectFrom("paste")
132
+
- .selectAll()
133
+
- .where("authorDid", "=", authorDid)
134
+
+ .leftJoin("comment", "comment.pasteUri", "paste.uri")
135
+
+ .select([
136
+
+ "paste.uri",
137
+
+ "paste.shortUrl",
138
+
+ "paste.authorDid as pasteAuthorDid",
139
+
+ "paste.code",
140
+
+ "paste.lang",
141
+
+ "paste.title",
142
+
+ "paste.createdAt as pasteCreatedAt",
143
+
+ "paste.indexedAt as pasteIndexedAt",
144
+
+ ctx.db.fn.count("comment.uri").as("commentCount")
145
+
+ ])
146
+
+ .groupBy("paste.uri")
147
+
+ .where("pasteAuthorDid", "=", authorDid)
148
+
+ .orderBy("pasteCreatedAt", "desc")
149
+
.execute();
150
+
let didHandleMap: Record<string, string> = {};
151
+
didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid);
152
+
diff --git a/src/views/index.pug b/src/views/index.pug
153
+
index 3443deb..e04403d 100644
154
+
--- a/src/views/index.pug
155
+
+++ b/src/views/index.pug
156
+
@@ -1,6 +1,7 @@
157
+
include ../mixins/mkPost
158
+
include ../mixins/head
159
+
include ../mixins/header
160
+
+include ../mixins/footer
161
+
include ../mixins/utils
162
+
include ../mixins/post
163
+
164
+
@@ -19,6 +20,7 @@ include ../mixins/post
165
+
"c",
166
+
"c#",
167
+
"c++",
168
+
+ "cobol",
169
+
].toSorted())
170
+
doctype html
171
+
html
172
+
@@ -34,3 +36,4 @@ html
173
+
each paste in pastes
174
+
- var handle = didHandleMap[paste.authorDid]
175
+
+post(paste, handle, paste.authorDid)
176
+
+ +footer()
177
+
diff --git a/src/views/paste.pug b/src/views/paste.pug
178
+
index 29516d3..f8a0906 100644
179
+
--- a/src/views/paste.pug
180
+
+++ b/src/views/paste.pug
181
+
@@ -15,12 +15,21 @@ html
182
+
| #{paste.lang} ·
183
+
| #{paste.code.split('\n').length} loc ·
184
+
a(href=`/r/${paste.shortUrl}`) raw
185
+
+ | ·
186
+
+ | #{comments.length} #{pluralize(comments.length, 'comment')}
187
+
pre
188
+
- | #{paste.code}
189
+
+ code
190
+
+ - var lines = paste.code.split(/\r?\n|\r|\n/g)
191
+
+ - var tot_chars = lines.length.toString().length
192
+
+ each line, idx in lines
193
+
+ span.code-line
194
+
+ span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`)
195
+
+ | #{idx + 1}
196
+
+ span.code-line-content #{line}
197
+
hr
198
+
199
+
if comments.length != 0
200
+
- h1 comments
201
+
+ h1(id="comments") comments
202
+
div.comments
203
+
each comment in comments
204
+
div.comment(id=`${encodeURIComponent(comment.uri)}`)
205
+
@@ -33,9 +42,14 @@ html
206
+
pre.comment-body #{comment.body}
207
+
hr
208
+
209
+
- form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
210
+
- div.post-row
211
+
- textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code
212
+
+ if ownDid
213
+
+ form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
214
+
+ div.post-row
215
+
+ textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code
216
+
217
+
- div.post-submit-row
218
+
- button(type="submit").post-input-submit zonk!
219
+
+ div.post-submit-row
220
+
+ button(type="submit").post-input-submit zonk!
221
+
+ else
222
+
+ p
223
+
+ a(href="/login") login
224
+
+ | to post a comment
225
+
--
226
+
2.47.0
227
+
228
+
From 436f4d5e8912c50068d8c70e623a23ce2ca9e6e7 Mon Sep 17 00:00:00 2001
229
+
From: Akshay <nerdy@peppe.rs>
230
+
Date: Thu, 26 Dec 2024 21:12:07 +0000
231
+
Subject: [PATCH 2/3] footer everywhere
232
+
233
+
---
234
+
src/views/index.pug | 1 +
235
+
src/views/login.pug | 2 ++
236
+
src/views/paste.pug | 6 ++++--
237
+
src/views/user.pug | 3 +++
238
+
4 files changed, 10 insertions(+), 2 deletions(-)
239
+
240
+
diff --git a/src/views/index.pug b/src/views/index.pug
241
+
index e04403d..bc9085a 100644
242
+
--- a/src/views/index.pug
243
+
+++ b/src/views/index.pug
244
+
@@ -36,4 +36,5 @@ html
245
+
each paste in pastes
246
+
- var handle = didHandleMap[paste.authorDid]
247
+
+post(paste, handle, paste.authorDid)
248
+
+
249
+
+footer()
250
+
diff --git a/src/views/login.pug b/src/views/login.pug
251
+
index 55aa048..b5a35e0 100644
252
+
--- a/src/views/login.pug
253
+
+++ b/src/views/login.pug
254
+
@@ -1,4 +1,5 @@
255
+
include ../mixins/head
256
+
+include ../mixins/footer
257
+
258
+
doctype html
259
+
html
260
+
@@ -10,3 +11,4 @@ html
261
+
div.login-row
262
+
input(type="text" name="handle" placeholder="enter handle" required).login-input-title
263
+
button(type="submit").login-submit-button login
264
+
+ +footer()
265
+
diff --git a/src/views/paste.pug b/src/views/paste.pug
266
+
index f8a0906..653c02e 100644
267
+
--- a/src/views/paste.pug
268
+
+++ b/src/views/paste.pug
269
+
@@ -1,6 +1,7 @@
270
+
- var now = new Date()
271
+
include ../mixins/head
272
+
include ../mixins/header
273
+
+include ../mixins/footer
274
+
include ../mixins/utils
275
+
doctype html
276
+
html
277
+
@@ -40,7 +41,6 @@ html
278
+
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
279
+
p
280
+
pre.comment-body #{comment.body}
281
+
- hr
282
+
283
+
if ownDid
284
+
form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
285
+
@@ -49,7 +49,9 @@ html
286
+
287
+
div.post-submit-row
288
+
button(type="submit").post-input-submit zonk!
289
+
- else
290
+
+ else
291
+
p
292
+
a(href="/login") login
293
+
| to post a comment
294
+
+
295
+
+ +footer()
296
+
diff --git a/src/views/user.pug b/src/views/user.pug
297
+
index 3523e16..b2b2743 100644
298
+
--- a/src/views/user.pug
299
+
+++ b/src/views/user.pug
300
+
@@ -2,6 +2,7 @@
301
+
- var handle = didHandleMap[authorDid]
302
+
include ../mixins/head
303
+
include ../mixins/header
304
+
+include ../mixins/footer
305
+
include ../mixins/utils
306
+
include ../mixins/post
307
+
doctype html
308
+
@@ -14,3 +15,5 @@ html
309
+
div.timeline
310
+
each paste in pastes
311
+
+post(paste, handle, authorDid)
312
+
+
313
+
+ +footer()
314
+
--
315
+
2.47.0
316
+
317
+
From a717f0702e818515e34187cde86c15c00d2e66ba Mon Sep 17 00:00:00 2001
318
+
From: Akshay <nerdy@peppe.rs>
319
+
Date: Thu, 26 Dec 2024 23:14:37 +0000
320
+
Subject: [PATCH 3/3] stylin for links
321
+
322
+
---
323
+
src/mixins/post.pug | 2 +-
324
+
src/public/styles.css | 51 ++++++++++++++++++++++++++++++++++++++++++-
325
+
src/views/paste.pug | 9 ++++----
326
+
3 files changed, 55 insertions(+), 7 deletions(-)
327
+
328
+
diff --git a/src/mixins/post.pug b/src/mixins/post.pug
329
+
index e98bcf8..51af1ff 100644
330
+
--- a/src/mixins/post.pug
331
+
+++ b/src/mixins/post.pug
332
+
@@ -1,7 +1,7 @@
333
+
mixin post(paste, handle, did)
334
+
div.post
335
+
p
336
+
- a(href=`/p/${paste.shortUrl}`)
337
+
+ a(href=`/p/${paste.shortUrl}`).post-link
338
+
| #{paste.title}
339
+
p.post-info
340
+
| by
341
+
diff --git a/src/public/styles.css b/src/public/styles.css
342
+
index 6f80f5f..b153f92 100644
343
+
--- a/src/public/styles.css
344
+
+++ b/src/public/styles.css
345
+
@@ -58,7 +58,6 @@ a:visited {
346
+
}
347
+
348
+
pre {
349
+
- background-color: var(--bg-color-muted);
350
+
padding: 1rem;
351
+
overflow-x: auto;
352
+
}
353
+
@@ -66,6 +65,11 @@ pre {
354
+
.comment-body {
355
+
background-color: var(--bg-color);
356
+
padding: 0;
357
+
+ margin-top: 0.1rem;
358
+
+}
359
+
+
360
+
+.comment-info {
361
+
+ margin-bottom: 0;
362
+
}
363
+
364
+
input, textarea, select, button {
365
+
@@ -137,10 +141,40 @@ hr {
366
+
align-self: flex-end;
367
+
}
368
+
369
+
+.post-link {
370
+
+ color: var(--text-color);
371
+
+ text-decoration: none;
372
+
+}
373
+
+.post-link:hover {
374
+
+ text-decoration: underline;
375
+
+}
376
+
+.post-link:visited {
377
+
+ color: var(--text-color-muted);
378
+
+}
379
+
+
380
+
+.post-info {
381
+
+ margin-top: 0;
382
+
+}
383
+
+
384
+
+.post-info, .comment-info {
385
+
+ color: var(--text-color-muted);
386
+
+}
387
+
+.post-info a, .comment-info a {
388
+
+ color: var(--text-color-muted);
389
+
+ text-decoration: none;
390
+
+}
391
+
+.post-info a:visited, .comment-info a:visited {
392
+
+ color: var(--text-color-muted);
393
+
+}
394
+
+.post-info a:hover, .comment-info a:hover {
395
+
+ text-decoration: underline;
396
+
+}
397
+
+
398
+
.timeline, .comments {
399
+
display: flex;
400
+
flex-direction: column;
401
+
gap: 1rem;
402
+
+ padding-bottom: 1rem;
403
+
}
404
+
405
+
.login-input-title {
406
+
@@ -182,3 +216,18 @@ select {
407
+
.code-line-content {
408
+
color: var(--text-color);
409
+
}
410
+
+
411
+
+.header, .footer {
412
+
+ color: var(--text-color);
413
+
+}
414
+
+
415
+
+.header a, .header a:visited,
416
+
+.footer a, .footer a:visited {
417
+
+ color: var(--link-color);
418
+
+ text-decoration: none;
419
+
+}
420
+
+
421
+
+.header a:hover,
422
+
+.footer a:hover {
423
+
+ text-decoration: underline;
424
+
+}
425
+
diff --git a/src/views/paste.pug b/src/views/paste.pug
426
+
index 653c02e..3014107 100644
427
+
--- a/src/views/paste.pug
428
+
+++ b/src/views/paste.pug
429
+
@@ -10,13 +10,13 @@ html
430
+
main#content
431
+
+header(ownDid, didHandleMap)
432
+
h1 #{paste.title}
433
+
- p
434
+
+ p.post-info
435
+
| @#{didHandleMap[paste.authorDid]} ·
436
+
| #{timeDifference(now, Date.parse(paste.createdAt))} ago ·
437
+
| #{paste.lang} ·
438
+
| #{paste.code.split('\n').length} loc ·
439
+
a(href=`/r/${paste.shortUrl}`) raw
440
+
- | ·
441
+
+ | ·
442
+
| #{comments.length} #{pluralize(comments.length, 'comment')}
443
+
pre
444
+
code
445
+
@@ -34,13 +34,12 @@ html
446
+
div.comments
447
+
each comment in comments
448
+
div.comment(id=`${encodeURIComponent(comment.uri)}`)
449
+
- p
450
+
+ p.comment-info
451
+
a(href=`/u/${comment.authorDid}`)
452
+
| @#{didHandleMap[comment.authorDid]}
453
+
| ·
454
+
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
455
+
- p
456
+
- pre.comment-body #{comment.body}
457
+
+ pre.comment-body #{comment.body}
458
+
459
+
if ownDid
460
+
form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
461
+
--
462
+
2.47.0
463
+
+465
y
+465
y
···
1
+
From 6df2fc36269c7114fb845fa0ac667e08f6e7d09b Mon Sep 17 00:00:00 2001
2
+
From: Akshay <nerdy@peppe.rs>
3
+
Date: Thu, 26 Dec 2024 19:19:37 +0000
4
+
Subject: [PATCH 1/3] add footer, comments, linenrs etc
5
+
6
+
---
7
+
src/mixins/footer.pug | 9 +++++++++
8
+
src/mixins/post.pug | 3 ++-
9
+
src/mixins/utils.pug | 4 ++++
10
+
src/public/styles.css | 21 +++++++++++++++++++--
11
+
src/routes.ts | 33 +++++++++++++++++++++++++++++----
12
+
src/views/index.pug | 3 +++
13
+
src/views/paste.pug | 28 +++++++++++++++++++++-------
14
+
7 files changed, 87 insertions(+), 14 deletions(-)
15
+
create mode 100644 src/mixins/footer.pug
16
+
17
+
diff --git a/src/mixins/footer.pug b/src/mixins/footer.pug
18
+
new file mode 100644
19
+
index 0000000..be71086
20
+
--- /dev/null
21
+
+++ b/src/mixins/footer.pug
22
+
@@ -0,0 +1,9 @@
23
+
+mixin footer()
24
+
+ hr
25
+
+ div.footer
26
+
+ div.left-side
27
+
+ div.right-side
28
+
+ p
29
+
+ | made by
30
+
+ a(href="https://bsky.app/profile/oppi.li") @oppi.li
31
+
+
32
+
diff --git a/src/mixins/post.pug b/src/mixins/post.pug
33
+
index 77d78aa..e98bcf8 100644
34
+
--- a/src/mixins/post.pug
35
+
+++ b/src/mixins/post.pug
36
+
@@ -13,4 +13,5 @@ mixin post(paste, handle, did)
37
+
| #{paste.lang}
38
+
| ·
39
+
| #{paste.code.split('\n').length} loc
40
+
-
41
+
+ | ·
42
+
+ a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')}
43
+
diff --git a/src/mixins/utils.pug b/src/mixins/utils.pug
44
+
index 857bddd..08507d3 100644
45
+
--- a/src/mixins/utils.pug
46
+
+++ b/src/mixins/utils.pug
47
+
@@ -2,6 +2,10 @@
48
+
function randInt(min, max) {
49
+
return Math.floor(Math.random() * (max - min + 1)) + min;
50
+
}
51
+
+-
52
+
+ function pluralize(count, noun) {
53
+
+ return count==1?noun:`${noun}s`;
54
+
+ }
55
+
-
56
+
function timeDifference(current, previous) {
57
+
if (!current || !previous) {
58
+
diff --git a/src/public/styles.css b/src/public/styles.css
59
+
index f88b533..6f80f5f 100644
60
+
--- a/src/public/styles.css
61
+
+++ b/src/public/styles.css
62
+
@@ -104,7 +104,6 @@ textarea {
63
+
hr {
64
+
border: none;
65
+
border-top: 1px solid var(--bg-color-muted);
66
+
- padding: 1rem;
67
+
}
68
+
69
+
.post-form {
70
+
@@ -152,7 +151,7 @@ hr {
71
+
flex: 1
72
+
}
73
+
74
+
-.header {
75
+
+.header, .footer {
76
+
display: flex;
77
+
flex-direction: row;
78
+
justify-content: space-between;
79
+
@@ -165,3 +164,21 @@ select {
80
+
text-indent: 1px;
81
+
text-overflow: '';
82
+
}
83
+
+
84
+
+.code-line {
85
+
+ display: flex;
86
+
+}
87
+
+
88
+
+.code-line-num {
89
+
+ white-space: pre;
90
+
+ -webkit-user-select: none;
91
+
+ user-select: none;
92
+
+ margin-right: 0.4em;
93
+
+ padding: 0 0.4em 0 0.4em;
94
+
+ color: var(--text-color-muted);
95
+
+ text-align: right;
96
+
+}
97
+
+
98
+
+.code-line-content {
99
+
+ color: var(--text-color);
100
+
+}
101
+
diff --git a/src/routes.ts b/src/routes.ts
102
+
index 70f931d..17fa00e 100644
103
+
--- a/src/routes.ts
104
+
+++ b/src/routes.ts
105
+
@@ -105,8 +105,20 @@ export const createRouter = (ctx: Ctx) => {
106
+
const agent = await getSessionAgent(req, res, ctx);
107
+
const pastes = await ctx.db
108
+
.selectFrom("paste")
109
+
- .selectAll()
110
+
- .orderBy("indexedAt", "desc")
111
+
+ .leftJoin("comment", "comment.pasteUri", "paste.uri")
112
+
+ .select([
113
+
+ "paste.uri",
114
+
+ "paste.shortUrl",
115
+
+ "paste.authorDid",
116
+
+ "paste.code",
117
+
+ "paste.lang",
118
+
+ "paste.title",
119
+
+ "paste.createdAt",
120
+
+ "paste.indexedAt as pasteIndexedAt",
121
+
+ ctx.db.fn.count("comment.uri").as("commentCount")
122
+
+ ])
123
+
+ .groupBy("paste.uri")
124
+
+ .orderBy("pasteIndexedAt", "desc")
125
+
.limit(25)
126
+
.execute();
127
+
128
+
@@ -130,8 +142,21 @@ export const createRouter = (ctx: Ctx) => {
129
+
const { authorDid } = req.params;
130
+
const pastes = await ctx.db
131
+
.selectFrom("paste")
132
+
- .selectAll()
133
+
- .where("authorDid", "=", authorDid)
134
+
+ .leftJoin("comment", "comment.pasteUri", "paste.uri")
135
+
+ .select([
136
+
+ "paste.uri",
137
+
+ "paste.shortUrl",
138
+
+ "paste.authorDid as pasteAuthorDid",
139
+
+ "paste.code",
140
+
+ "paste.lang",
141
+
+ "paste.title",
142
+
+ "paste.createdAt as pasteCreatedAt",
143
+
+ "paste.indexedAt as pasteIndexedAt",
144
+
+ ctx.db.fn.count("comment.uri").as("commentCount")
145
+
+ ])
146
+
+ .groupBy("paste.uri")
147
+
+ .where("pasteAuthorDid", "=", authorDid)
148
+
+ .orderBy("pasteCreatedAt", "desc")
149
+
.execute();
150
+
let didHandleMap: Record<string, string> = {};
151
+
didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid);
152
+
diff --git a/src/views/index.pug b/src/views/index.pug
153
+
index 3443deb..e04403d 100644
154
+
--- a/src/views/index.pug
155
+
+++ b/src/views/index.pug
156
+
@@ -1,6 +1,7 @@
157
+
include ../mixins/mkPost
158
+
include ../mixins/head
159
+
include ../mixins/header
160
+
+include ../mixins/footer
161
+
include ../mixins/utils
162
+
include ../mixins/post
163
+
164
+
@@ -19,6 +20,7 @@ include ../mixins/post
165
+
"c",
166
+
"c#",
167
+
"c++",
168
+
+ "cobol",
169
+
].toSorted())
170
+
doctype html
171
+
html
172
+
@@ -34,3 +36,4 @@ html
173
+
each paste in pastes
174
+
- var handle = didHandleMap[paste.authorDid]
175
+
+post(paste, handle, paste.authorDid)
176
+
+ +footer()
177
+
diff --git a/src/views/paste.pug b/src/views/paste.pug
178
+
index 29516d3..f8a0906 100644
179
+
--- a/src/views/paste.pug
180
+
+++ b/src/views/paste.pug
181
+
@@ -15,12 +15,21 @@ html
182
+
| #{paste.lang} ·
183
+
| #{paste.code.split('\n').length} loc ·
184
+
a(href=`/r/${paste.shortUrl}`) raw
185
+
+ | ·
186
+
+ | #{comments.length} #{pluralize(comments.length, 'comment')}
187
+
pre
188
+
- | #{paste.code}
189
+
+ code
190
+
+ - var lines = paste.code.split(/\r?\n|\r|\n/g)
191
+
+ - var tot_chars = lines.length.toString().length
192
+
+ each line, idx in lines
193
+
+ span.code-line
194
+
+ span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`)
195
+
+ | #{idx + 1}
196
+
+ span.code-line-content #{line}
197
+
hr
198
+
199
+
if comments.length != 0
200
+
- h1 comments
201
+
+ h1(id="comments") comments
202
+
div.comments
203
+
each comment in comments
204
+
div.comment(id=`${encodeURIComponent(comment.uri)}`)
205
+
@@ -33,9 +42,14 @@ html
206
+
pre.comment-body #{comment.body}
207
+
hr
208
+
209
+
- form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
210
+
- div.post-row
211
+
- textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code
212
+
+ if ownDid
213
+
+ form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
214
+
+ div.post-row
215
+
+ textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code
216
+
217
+
- div.post-submit-row
218
+
- button(type="submit").post-input-submit zonk!
219
+
+ div.post-submit-row
220
+
+ button(type="submit").post-input-submit zonk!
221
+
+ else
222
+
+ p
223
+
+ a(href="/login") login
224
+
+ | to post a comment
225
+
--
226
+
2.47.0
227
+
228
+
229
+
From ccd3269be5d287b627c8badc96bd0ce5f0d3a0bf Mon Sep 17 00:00:00 2001
230
+
From: Akshay <nerdy@peppe.rs>
231
+
Date: Thu, 26 Dec 2024 21:12:07 +0000
232
+
Subject: [PATCH 2/3] footer everywhere
233
+
234
+
---
235
+
src/views/index.pug | 1 +
236
+
src/views/login.pug | 2 ++
237
+
src/views/paste.pug | 6 ++++--
238
+
src/views/user.pug | 3 +++
239
+
4 files changed, 10 insertions(+), 2 deletions(-)
240
+
241
+
diff --git a/src/views/index.pug b/src/views/index.pug
242
+
index e04403d..bc9085a 100644
243
+
--- a/src/views/index.pug
244
+
+++ b/src/views/index.pug
245
+
@@ -36,4 +36,5 @@ html
246
+
each paste in pastes
247
+
- var handle = didHandleMap[paste.authorDid]
248
+
+post(paste, handle, paste.authorDid)
249
+
+
250
+
+footer()
251
+
diff --git a/src/views/login.pug b/src/views/login.pug
252
+
index 55aa048..b5a35e0 100644
253
+
--- a/src/views/login.pug
254
+
+++ b/src/views/login.pug
255
+
@@ -1,4 +1,5 @@
256
+
include ../mixins/head
257
+
+include ../mixins/footer
258
+
259
+
doctype html
260
+
html
261
+
@@ -10,3 +11,4 @@ html
262
+
div.login-row
263
+
input(type="text" name="handle" placeholder="enter handle" required).login-input-title
264
+
button(type="submit").login-submit-button login
265
+
+ +footer()
266
+
diff --git a/src/views/paste.pug b/src/views/paste.pug
267
+
index f8a0906..653c02e 100644
268
+
--- a/src/views/paste.pug
269
+
+++ b/src/views/paste.pug
270
+
@@ -1,6 +1,7 @@
271
+
- var now = new Date()
272
+
include ../mixins/head
273
+
include ../mixins/header
274
+
+include ../mixins/footer
275
+
include ../mixins/utils
276
+
doctype html
277
+
html
278
+
@@ -40,7 +41,6 @@ html
279
+
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
280
+
p
281
+
pre.comment-body #{comment.body}
282
+
- hr
283
+
284
+
if ownDid
285
+
form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
286
+
@@ -49,7 +49,9 @@ html
287
+
288
+
div.post-submit-row
289
+
button(type="submit").post-input-submit zonk!
290
+
- else
291
+
+ else
292
+
p
293
+
a(href="/login") login
294
+
| to post a comment
295
+
+
296
+
+ +footer()
297
+
diff --git a/src/views/user.pug b/src/views/user.pug
298
+
index 3523e16..b2b2743 100644
299
+
--- a/src/views/user.pug
300
+
+++ b/src/views/user.pug
301
+
@@ -2,6 +2,7 @@
302
+
- var handle = didHandleMap[authorDid]
303
+
include ../mixins/head
304
+
include ../mixins/header
305
+
+include ../mixins/footer
306
+
include ../mixins/utils
307
+
include ../mixins/post
308
+
doctype html
309
+
@@ -14,3 +15,5 @@ html
310
+
div.timeline
311
+
each paste in pastes
312
+
+post(paste, handle, authorDid)
313
+
+
314
+
+ +footer()
315
+
--
316
+
2.47.0
317
+
318
+
319
+
From 0d1ee81ee0630ca852f51a9a49293fb7d0fc7a67 Mon Sep 17 00:00:00 2001
320
+
From: Akshay <nerdy@peppe.rs>
321
+
Date: Thu, 26 Dec 2024 23:14:37 +0000
322
+
Subject: [PATCH 3/3] stylin for links
323
+
324
+
---
325
+
src/mixins/post.pug | 2 +-
326
+
src/public/styles.css | 51 ++++++++++++++++++++++++++++++++++++++++++-
327
+
src/views/paste.pug | 9 ++++----
328
+
3 files changed, 55 insertions(+), 7 deletions(-)
329
+
330
+
diff --git a/src/mixins/post.pug b/src/mixins/post.pug
331
+
index e98bcf8..51af1ff 100644
332
+
--- a/src/mixins/post.pug
333
+
+++ b/src/mixins/post.pug
334
+
@@ -1,7 +1,7 @@
335
+
mixin post(paste, handle, did)
336
+
div.post
337
+
p
338
+
- a(href=`/p/${paste.shortUrl}`)
339
+
+ a(href=`/p/${paste.shortUrl}`).post-link
340
+
| #{paste.title}
341
+
p.post-info
342
+
| by
343
+
diff --git a/src/public/styles.css b/src/public/styles.css
344
+
index 6f80f5f..b153f92 100644
345
+
--- a/src/public/styles.css
346
+
+++ b/src/public/styles.css
347
+
@@ -58,7 +58,6 @@ a:visited {
348
+
}
349
+
350
+
pre {
351
+
- background-color: var(--bg-color-muted);
352
+
padding: 1rem;
353
+
overflow-x: auto;
354
+
}
355
+
@@ -66,6 +65,11 @@ pre {
356
+
.comment-body {
357
+
background-color: var(--bg-color);
358
+
padding: 0;
359
+
+ margin-top: 0.1rem;
360
+
+}
361
+
+
362
+
+.comment-info {
363
+
+ margin-bottom: 0;
364
+
}
365
+
366
+
input, textarea, select, button {
367
+
@@ -137,10 +141,40 @@ hr {
368
+
align-self: flex-end;
369
+
}
370
+
371
+
+.post-link {
372
+
+ color: var(--text-color);
373
+
+ text-decoration: none;
374
+
+}
375
+
+.post-link:hover {
376
+
+ text-decoration: underline;
377
+
+}
378
+
+.post-link:visited {
379
+
+ color: var(--text-color-muted);
380
+
+}
381
+
+
382
+
+.post-info {
383
+
+ margin-top: 0;
384
+
+}
385
+
+
386
+
+.post-info, .comment-info {
387
+
+ color: var(--text-color-muted);
388
+
+}
389
+
+.post-info a, .comment-info a {
390
+
+ color: var(--text-color-muted);
391
+
+ text-decoration: none;
392
+
+}
393
+
+.post-info a:visited, .comment-info a:visited {
394
+
+ color: var(--text-color-muted);
395
+
+}
396
+
+.post-info a:hover, .comment-info a:hover {
397
+
+ text-decoration: underline;
398
+
+}
399
+
+
400
+
.timeline, .comments {
401
+
display: flex;
402
+
flex-direction: column;
403
+
gap: 1rem;
404
+
+ padding-bottom: 1rem;
405
+
}
406
+
407
+
.login-input-title {
408
+
@@ -182,3 +216,18 @@ select {
409
+
.code-line-content {
410
+
color: var(--text-color);
411
+
}
412
+
+
413
+
+.header, .footer {
414
+
+ color: var(--text-color);
415
+
+}
416
+
+
417
+
+.header a, .header a:visited,
418
+
+.footer a, .footer a:visited {
419
+
+ color: var(--link-color);
420
+
+ text-decoration: none;
421
+
+}
422
+
+
423
+
+.header a:hover,
424
+
+.footer a:hover {
425
+
+ text-decoration: underline;
426
+
+}
427
+
diff --git a/src/views/paste.pug b/src/views/paste.pug
428
+
index 653c02e..3014107 100644
429
+
--- a/src/views/paste.pug
430
+
+++ b/src/views/paste.pug
431
+
@@ -10,13 +10,13 @@ html
432
+
main#content
433
+
+header(ownDid, didHandleMap)
434
+
h1 #{paste.title}
435
+
- p
436
+
+ p.post-info
437
+
| @#{didHandleMap[paste.authorDid]} ·
438
+
| #{timeDifference(now, Date.parse(paste.createdAt))} ago ·
439
+
| #{paste.lang} ·
440
+
| #{paste.code.split('\n').length} loc ·
441
+
a(href=`/r/${paste.shortUrl}`) raw
442
+
- | ·
443
+
+ | ·
444
+
| #{comments.length} #{pluralize(comments.length, 'comment')}
445
+
pre
446
+
code
447
+
@@ -34,13 +34,12 @@ html
448
+
div.comments
449
+
each comment in comments
450
+
div.comment(id=`${encodeURIComponent(comment.uri)}`)
451
+
- p
452
+
+ p.comment-info
453
+
a(href=`/u/${comment.authorDid}`)
454
+
| @#{didHandleMap[comment.authorDid]}
455
+
| ·
456
+
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
457
+
- p
458
+
- pre.comment-body #{comment.body}
459
+
+ pre.comment-body #{comment.body}
460
+
461
+
if ownDid
462
+
form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
463
+
--
464
+
2.47.0
465
+