+86
-1
flake.nix
+86
-1
flake.nix
···
10
10
supportedSystems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin"];
11
11
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
12
12
nixpkgsFor = forAllSystems (system:
13
-
import nixpkgs { inherit system; });
13
+
import nixpkgs {
14
+
inherit system;
15
+
overlays = [self.overlays.default];
16
+
});
14
17
in {
18
+
overlays.default = final: prev: let
19
+
pname = "plonk";
20
+
version = "0.1.0";
21
+
in {
22
+
plonk = with final;
23
+
buildNpmPackage {
24
+
inherit pname version;
25
+
src = ./.;
26
+
nativeBuildInputs = [makeBinaryWrapper];
27
+
packageJson = ./package.json;
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
+
40
+
npmDepsHash = "sha256-qGCbaFAHd/s9hOTWMjHCam6Kf6pU6IWPybfwYh0sOwc=";
41
+
};
42
+
};
43
+
44
+
packages = forAllSystems (system: {
45
+
inherit (nixpkgsFor."${system}") plonk;
46
+
});
47
+
48
+
defaultPackage = forAllSystems (system: nixpkgsFor."${system}".plonk);
15
49
16
50
devShell = forAllSystems (system: let
17
51
pkgs = nixpkgsFor."${system}";
···
26
60
});
27
61
28
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
+
};
29
114
};
30
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
+
}
+18
-3
lexicons/paste.json
+18
-3
lexicons/paste.json
···
1
1
{
2
2
"lexicon": 1,
3
-
"id": "ovh.plonk.paste",
3
+
"id": "li.plonk.paste",
4
4
"defs": {
5
5
"main": {
6
6
"type": "record",
7
7
"key": "tid",
8
8
"record": {
9
9
"type": "object",
10
-
"required": ["code", "lang", "title", "createdAt"],
10
+
"required": [
11
+
"code",
12
+
"shortUrl",
13
+
"lang",
14
+
"title",
15
+
"createdAt"
16
+
],
11
17
"properties": {
12
18
"code": {
13
19
"type": "string",
···
15
21
"maxGraphemes": 65536,
16
22
"maxLength": 65536
17
23
},
24
+
"shortUrl": {
25
+
"type": "string",
26
+
"minLength": 2,
27
+
"maxGraphemes": 10,
28
+
"maxLength": 10
29
+
},
18
30
"lang": {
19
31
"type": "string",
20
32
"minLength": 1,
···
27
39
"maxGraphemes": 100,
28
40
"maxLength": 100
29
41
},
30
-
"createdAt": { "type": "string", "format": "datetime" }
42
+
"createdAt": {
43
+
"type": "string",
44
+
"format": "datetime"
45
+
}
31
46
}
32
47
}
33
48
}
+523
package-lock.json
+523
package-lock.json
···
26
26
"multiformats": "^9.9.0",
27
27
"pino": "^9.3.2",
28
28
"pug": "^3.0.3",
29
+
"shiki": "^3.4.2",
29
30
"uhtml": "^4.5.9"
30
31
},
31
32
"devDependencies": {
···
1484
1485
"win32"
1485
1486
]
1486
1487
},
1488
+
"node_modules/@shikijs/core": {
1489
+
"version": "3.4.2",
1490
+
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.4.2.tgz",
1491
+
"integrity": "sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ==",
1492
+
"license": "MIT",
1493
+
"dependencies": {
1494
+
"@shikijs/types": "3.4.2",
1495
+
"@shikijs/vscode-textmate": "^10.0.2",
1496
+
"@types/hast": "^3.0.4",
1497
+
"hast-util-to-html": "^9.0.5"
1498
+
}
1499
+
},
1500
+
"node_modules/@shikijs/engine-javascript": {
1501
+
"version": "3.4.2",
1502
+
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.4.2.tgz",
1503
+
"integrity": "sha512-1/adJbSMBOkpScCE/SB6XkjJU17ANln3Wky7lOmrnpl+zBdQ1qXUJg2GXTYVHRq+2j3hd1DesmElTXYDgtfSOQ==",
1504
+
"license": "MIT",
1505
+
"dependencies": {
1506
+
"@shikijs/types": "3.4.2",
1507
+
"@shikijs/vscode-textmate": "^10.0.2",
1508
+
"oniguruma-to-es": "^4.3.3"
1509
+
}
1510
+
},
1511
+
"node_modules/@shikijs/engine-oniguruma": {
1512
+
"version": "3.4.2",
1513
+
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.4.2.tgz",
1514
+
"integrity": "sha512-zcZKMnNndgRa3ORja6Iemsr3DrLtkX3cAF7lTJkdMB6v9alhlBsX9uNiCpqofNrXOvpA3h6lHcLJxgCIhVOU5Q==",
1515
+
"license": "MIT",
1516
+
"dependencies": {
1517
+
"@shikijs/types": "3.4.2",
1518
+
"@shikijs/vscode-textmate": "^10.0.2"
1519
+
}
1520
+
},
1521
+
"node_modules/@shikijs/langs": {
1522
+
"version": "3.4.2",
1523
+
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.4.2.tgz",
1524
+
"integrity": "sha512-H6azIAM+OXD98yztIfs/KH5H4PU39t+SREhmM8LaNXyUrqj2mx+zVkr8MWYqjceSjDw9I1jawm1WdFqU806rMA==",
1525
+
"license": "MIT",
1526
+
"dependencies": {
1527
+
"@shikijs/types": "3.4.2"
1528
+
}
1529
+
},
1530
+
"node_modules/@shikijs/themes": {
1531
+
"version": "3.4.2",
1532
+
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.4.2.tgz",
1533
+
"integrity": "sha512-qAEuAQh+brd8Jyej2UDDf+b4V2g1Rm8aBIdvt32XhDPrHvDkEnpb7Kzc9hSuHUxz0Iuflmq7elaDuQAP9bHIhg==",
1534
+
"license": "MIT",
1535
+
"dependencies": {
1536
+
"@shikijs/types": "3.4.2"
1537
+
}
1538
+
},
1539
+
"node_modules/@shikijs/types": {
1540
+
"version": "3.4.2",
1541
+
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.4.2.tgz",
1542
+
"integrity": "sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg==",
1543
+
"license": "MIT",
1544
+
"dependencies": {
1545
+
"@shikijs/vscode-textmate": "^10.0.2",
1546
+
"@types/hast": "^3.0.4"
1547
+
}
1548
+
},
1549
+
"node_modules/@shikijs/vscode-textmate": {
1550
+
"version": "10.0.2",
1551
+
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
1552
+
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
1553
+
"license": "MIT"
1554
+
},
1487
1555
"node_modules/@ts-morph/common": {
1488
1556
"version": "0.17.0",
1489
1557
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.17.0.tgz",
···
1602
1670
"@types/send": "*"
1603
1671
}
1604
1672
},
1673
+
"node_modules/@types/hast": {
1674
+
"version": "3.0.4",
1675
+
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
1676
+
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
1677
+
"license": "MIT",
1678
+
"dependencies": {
1679
+
"@types/unist": "*"
1680
+
}
1681
+
},
1605
1682
"node_modules/@types/http-errors": {
1606
1683
"version": "2.0.4",
1607
1684
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
1608
1685
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
1609
1686
"dev": true,
1610
1687
"license": "MIT"
1688
+
},
1689
+
"node_modules/@types/mdast": {
1690
+
"version": "4.0.4",
1691
+
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
1692
+
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
1693
+
"license": "MIT",
1694
+
"dependencies": {
1695
+
"@types/unist": "*"
1696
+
}
1611
1697
},
1612
1698
"node_modules/@types/mime": {
1613
1699
"version": "1.3.5",
···
1662
1748
"@types/node": "*",
1663
1749
"@types/send": "*"
1664
1750
}
1751
+
},
1752
+
"node_modules/@types/unist": {
1753
+
"version": "3.0.3",
1754
+
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
1755
+
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
1756
+
"license": "MIT"
1757
+
},
1758
+
"node_modules/@ungap/structured-clone": {
1759
+
"version": "1.3.0",
1760
+
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
1761
+
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
1762
+
"license": "ISC"
1665
1763
},
1666
1764
"node_modules/@webreflection/signal": {
1667
1765
"version": "2.1.2",
···
2095
2193
"cborg": "cli.js"
2096
2194
}
2097
2195
},
2196
+
"node_modules/ccount": {
2197
+
"version": "2.0.1",
2198
+
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
2199
+
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
2200
+
"license": "MIT",
2201
+
"funding": {
2202
+
"type": "github",
2203
+
"url": "https://github.com/sponsors/wooorm"
2204
+
}
2205
+
},
2098
2206
"node_modules/chalk": {
2099
2207
"version": "4.1.2",
2100
2208
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
···
2110
2218
},
2111
2219
"funding": {
2112
2220
"url": "https://github.com/chalk/chalk?sponsor=1"
2221
+
}
2222
+
},
2223
+
"node_modules/character-entities-html4": {
2224
+
"version": "2.1.0",
2225
+
"resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
2226
+
"integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
2227
+
"license": "MIT",
2228
+
"funding": {
2229
+
"type": "github",
2230
+
"url": "https://github.com/sponsors/wooorm"
2231
+
}
2232
+
},
2233
+
"node_modules/character-entities-legacy": {
2234
+
"version": "3.0.0",
2235
+
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
2236
+
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
2237
+
"license": "MIT",
2238
+
"funding": {
2239
+
"type": "github",
2240
+
"url": "https://github.com/sponsors/wooorm"
2113
2241
}
2114
2242
},
2115
2243
"node_modules/character-parser": {
···
2189
2317
"node": ">= 0.8"
2190
2318
}
2191
2319
},
2320
+
"node_modules/comma-separated-tokens": {
2321
+
"version": "2.0.3",
2322
+
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
2323
+
"integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
2324
+
"license": "MIT",
2325
+
"funding": {
2326
+
"type": "github",
2327
+
"url": "https://github.com/sponsors/wooorm"
2328
+
}
2329
+
},
2192
2330
"node_modules/commander": {
2193
2331
"version": "9.5.0",
2194
2332
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
···
2361
2499
"node": ">= 0.8"
2362
2500
}
2363
2501
},
2502
+
"node_modules/dequal": {
2503
+
"version": "2.0.3",
2504
+
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
2505
+
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
2506
+
"license": "MIT",
2507
+
"engines": {
2508
+
"node": ">=6"
2509
+
}
2510
+
},
2364
2511
"node_modules/destroy": {
2365
2512
"version": "1.2.0",
2366
2513
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
···
2378
2525
"license": "Apache-2.0",
2379
2526
"engines": {
2380
2527
"node": ">=8"
2528
+
}
2529
+
},
2530
+
"node_modules/devlop": {
2531
+
"version": "1.1.0",
2532
+
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
2533
+
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
2534
+
"license": "MIT",
2535
+
"dependencies": {
2536
+
"dequal": "^2.0.0"
2537
+
},
2538
+
"funding": {
2539
+
"type": "github",
2540
+
"url": "https://github.com/sponsors/wooorm"
2381
2541
}
2382
2542
},
2383
2543
"node_modules/diff": {
···
3043
3203
"node": ">= 0.4"
3044
3204
}
3045
3205
},
3206
+
"node_modules/hast-util-to-html": {
3207
+
"version": "9.0.5",
3208
+
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
3209
+
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
3210
+
"license": "MIT",
3211
+
"dependencies": {
3212
+
"@types/hast": "^3.0.0",
3213
+
"@types/unist": "^3.0.0",
3214
+
"ccount": "^2.0.0",
3215
+
"comma-separated-tokens": "^2.0.0",
3216
+
"hast-util-whitespace": "^3.0.0",
3217
+
"html-void-elements": "^3.0.0",
3218
+
"mdast-util-to-hast": "^13.0.0",
3219
+
"property-information": "^7.0.0",
3220
+
"space-separated-tokens": "^2.0.0",
3221
+
"stringify-entities": "^4.0.0",
3222
+
"zwitch": "^2.0.4"
3223
+
},
3224
+
"funding": {
3225
+
"type": "opencollective",
3226
+
"url": "https://opencollective.com/unified"
3227
+
}
3228
+
},
3229
+
"node_modules/hast-util-whitespace": {
3230
+
"version": "3.0.0",
3231
+
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
3232
+
"integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
3233
+
"license": "MIT",
3234
+
"dependencies": {
3235
+
"@types/hast": "^3.0.0"
3236
+
},
3237
+
"funding": {
3238
+
"type": "opencollective",
3239
+
"url": "https://opencollective.com/unified"
3240
+
}
3241
+
},
3046
3242
"node_modules/help-me": {
3047
3243
"version": "5.0.0",
3048
3244
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
···
3056
3252
"integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==",
3057
3253
"license": "MIT"
3058
3254
},
3255
+
"node_modules/html-void-elements": {
3256
+
"version": "3.0.0",
3257
+
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
3258
+
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
3259
+
"license": "MIT",
3260
+
"funding": {
3261
+
"type": "github",
3262
+
"url": "https://github.com/sponsors/wooorm"
3263
+
}
3264
+
},
3059
3265
"node_modules/htmlparser2": {
3060
3266
"version": "9.1.0",
3061
3267
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
···
3404
3610
"dev": true,
3405
3611
"license": "ISC"
3406
3612
},
3613
+
"node_modules/mdast-util-to-hast": {
3614
+
"version": "13.2.0",
3615
+
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
3616
+
"integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
3617
+
"license": "MIT",
3618
+
"dependencies": {
3619
+
"@types/hast": "^3.0.0",
3620
+
"@types/mdast": "^4.0.0",
3621
+
"@ungap/structured-clone": "^1.0.0",
3622
+
"devlop": "^1.0.0",
3623
+
"micromark-util-sanitize-uri": "^2.0.0",
3624
+
"trim-lines": "^3.0.0",
3625
+
"unist-util-position": "^5.0.0",
3626
+
"unist-util-visit": "^5.0.0",
3627
+
"vfile": "^6.0.0"
3628
+
},
3629
+
"funding": {
3630
+
"type": "opencollective",
3631
+
"url": "https://opencollective.com/unified"
3632
+
}
3633
+
},
3407
3634
"node_modules/media-typer": {
3408
3635
"version": "0.3.0",
3409
3636
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
···
3441
3668
"node": ">= 0.6"
3442
3669
}
3443
3670
},
3671
+
"node_modules/micromark-util-character": {
3672
+
"version": "2.1.1",
3673
+
"resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
3674
+
"integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
3675
+
"funding": [
3676
+
{
3677
+
"type": "GitHub Sponsors",
3678
+
"url": "https://github.com/sponsors/unifiedjs"
3679
+
},
3680
+
{
3681
+
"type": "OpenCollective",
3682
+
"url": "https://opencollective.com/unified"
3683
+
}
3684
+
],
3685
+
"license": "MIT",
3686
+
"dependencies": {
3687
+
"micromark-util-symbol": "^2.0.0",
3688
+
"micromark-util-types": "^2.0.0"
3689
+
}
3690
+
},
3691
+
"node_modules/micromark-util-encode": {
3692
+
"version": "2.0.1",
3693
+
"resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
3694
+
"integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
3695
+
"funding": [
3696
+
{
3697
+
"type": "GitHub Sponsors",
3698
+
"url": "https://github.com/sponsors/unifiedjs"
3699
+
},
3700
+
{
3701
+
"type": "OpenCollective",
3702
+
"url": "https://opencollective.com/unified"
3703
+
}
3704
+
],
3705
+
"license": "MIT"
3706
+
},
3707
+
"node_modules/micromark-util-sanitize-uri": {
3708
+
"version": "2.0.1",
3709
+
"resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
3710
+
"integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
3711
+
"funding": [
3712
+
{
3713
+
"type": "GitHub Sponsors",
3714
+
"url": "https://github.com/sponsors/unifiedjs"
3715
+
},
3716
+
{
3717
+
"type": "OpenCollective",
3718
+
"url": "https://opencollective.com/unified"
3719
+
}
3720
+
],
3721
+
"license": "MIT",
3722
+
"dependencies": {
3723
+
"micromark-util-character": "^2.0.0",
3724
+
"micromark-util-encode": "^2.0.0",
3725
+
"micromark-util-symbol": "^2.0.0"
3726
+
}
3727
+
},
3728
+
"node_modules/micromark-util-symbol": {
3729
+
"version": "2.0.1",
3730
+
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
3731
+
"integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
3732
+
"funding": [
3733
+
{
3734
+
"type": "GitHub Sponsors",
3735
+
"url": "https://github.com/sponsors/unifiedjs"
3736
+
},
3737
+
{
3738
+
"type": "OpenCollective",
3739
+
"url": "https://opencollective.com/unified"
3740
+
}
3741
+
],
3742
+
"license": "MIT"
3743
+
},
3744
+
"node_modules/micromark-util-types": {
3745
+
"version": "2.0.2",
3746
+
"resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
3747
+
"integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
3748
+
"funding": [
3749
+
{
3750
+
"type": "GitHub Sponsors",
3751
+
"url": "https://github.com/sponsors/unifiedjs"
3752
+
},
3753
+
{
3754
+
"type": "OpenCollective",
3755
+
"url": "https://opencollective.com/unified"
3756
+
}
3757
+
],
3758
+
"license": "MIT"
3759
+
},
3444
3760
"node_modules/micromatch": {
3445
3761
"version": "4.0.8",
3446
3762
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
···
3671
3987
"wrappy": "1"
3672
3988
}
3673
3989
},
3990
+
"node_modules/oniguruma-parser": {
3991
+
"version": "0.12.1",
3992
+
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
3993
+
"integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==",
3994
+
"license": "MIT"
3995
+
},
3996
+
"node_modules/oniguruma-to-es": {
3997
+
"version": "4.3.3",
3998
+
"resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz",
3999
+
"integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==",
4000
+
"license": "MIT",
4001
+
"dependencies": {
4002
+
"oniguruma-parser": "^0.12.1",
4003
+
"regex": "^6.0.1",
4004
+
"regex-recursion": "^6.0.2"
4005
+
}
4006
+
},
3674
4007
"node_modules/p-finally": {
3675
4008
"version": "1.0.0",
3676
4009
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
···
3972
4305
"asap": "~2.0.3"
3973
4306
}
3974
4307
},
4308
+
"node_modules/property-information": {
4309
+
"version": "7.1.0",
4310
+
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
4311
+
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
4312
+
"license": "MIT",
4313
+
"funding": {
4314
+
"type": "github",
4315
+
"url": "https://github.com/sponsors/wooorm"
4316
+
}
4317
+
},
3975
4318
"node_modules/proxy-addr": {
3976
4319
"version": "2.0.7",
3977
4320
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
···
4284
4627
"node": ">= 12.13.0"
4285
4628
}
4286
4629
},
4630
+
"node_modules/regex": {
4631
+
"version": "6.0.1",
4632
+
"resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz",
4633
+
"integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==",
4634
+
"license": "MIT",
4635
+
"dependencies": {
4636
+
"regex-utilities": "^2.3.0"
4637
+
}
4638
+
},
4639
+
"node_modules/regex-recursion": {
4640
+
"version": "6.0.2",
4641
+
"resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz",
4642
+
"integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
4643
+
"license": "MIT",
4644
+
"dependencies": {
4645
+
"regex-utilities": "^2.3.0"
4646
+
}
4647
+
},
4648
+
"node_modules/regex-utilities": {
4649
+
"version": "2.3.0",
4650
+
"resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
4651
+
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
4652
+
"license": "MIT"
4653
+
},
4287
4654
"node_modules/resolve": {
4288
4655
"version": "1.22.8",
4289
4656
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
···
4564
4931
"node": ">=8"
4565
4932
}
4566
4933
},
4934
+
"node_modules/shiki": {
4935
+
"version": "3.4.2",
4936
+
"resolved": "https://registry.npmjs.org/shiki/-/shiki-3.4.2.tgz",
4937
+
"integrity": "sha512-wuxzZzQG8kvZndD7nustrNFIKYJ1jJoWIPaBpVe2+KHSvtzMi4SBjOxrigs8qeqce/l3U0cwiC+VAkLKSunHQQ==",
4938
+
"license": "MIT",
4939
+
"dependencies": {
4940
+
"@shikijs/core": "3.4.2",
4941
+
"@shikijs/engine-javascript": "3.4.2",
4942
+
"@shikijs/engine-oniguruma": "3.4.2",
4943
+
"@shikijs/langs": "3.4.2",
4944
+
"@shikijs/themes": "3.4.2",
4945
+
"@shikijs/types": "3.4.2",
4946
+
"@shikijs/vscode-textmate": "^10.0.2",
4947
+
"@types/hast": "^3.0.4"
4948
+
}
4949
+
},
4567
4950
"node_modules/side-channel": {
4568
4951
"version": "1.0.6",
4569
4952
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
···
4660
5043
},
4661
5044
"engines": {
4662
5045
"node": ">= 8"
5046
+
}
5047
+
},
5048
+
"node_modules/space-separated-tokens": {
5049
+
"version": "2.0.2",
5050
+
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
5051
+
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
5052
+
"license": "MIT",
5053
+
"funding": {
5054
+
"type": "github",
5055
+
"url": "https://github.com/sponsors/wooorm"
4663
5056
}
4664
5057
},
4665
5058
"node_modules/split2": {
···
4753
5146
"node": ">=8"
4754
5147
}
4755
5148
},
5149
+
"node_modules/stringify-entities": {
5150
+
"version": "4.0.4",
5151
+
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
5152
+
"integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
5153
+
"license": "MIT",
5154
+
"dependencies": {
5155
+
"character-entities-html4": "^2.0.0",
5156
+
"character-entities-legacy": "^3.0.0"
5157
+
},
5158
+
"funding": {
5159
+
"type": "github",
5160
+
"url": "https://github.com/sponsors/wooorm"
5161
+
}
5162
+
},
4756
5163
"node_modules/strip-ansi": {
4757
5164
"version": "7.1.0",
4758
5165
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
···
5042
5449
"license": "MIT",
5043
5450
"bin": {
5044
5451
"tree-kill": "cli.js"
5452
+
}
5453
+
},
5454
+
"node_modules/trim-lines": {
5455
+
"version": "3.0.1",
5456
+
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
5457
+
"integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
5458
+
"license": "MIT",
5459
+
"funding": {
5460
+
"type": "github",
5461
+
"url": "https://github.com/sponsors/wooorm"
5045
5462
}
5046
5463
},
5047
5464
"node_modules/ts-interface-checker": {
···
5752
6169
"dev": true,
5753
6170
"license": "MIT"
5754
6171
},
6172
+
"node_modules/unist-util-is": {
6173
+
"version": "6.0.0",
6174
+
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
6175
+
"integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
6176
+
"license": "MIT",
6177
+
"dependencies": {
6178
+
"@types/unist": "^3.0.0"
6179
+
},
6180
+
"funding": {
6181
+
"type": "opencollective",
6182
+
"url": "https://opencollective.com/unified"
6183
+
}
6184
+
},
6185
+
"node_modules/unist-util-position": {
6186
+
"version": "5.0.0",
6187
+
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
6188
+
"integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
6189
+
"license": "MIT",
6190
+
"dependencies": {
6191
+
"@types/unist": "^3.0.0"
6192
+
},
6193
+
"funding": {
6194
+
"type": "opencollective",
6195
+
"url": "https://opencollective.com/unified"
6196
+
}
6197
+
},
6198
+
"node_modules/unist-util-stringify-position": {
6199
+
"version": "4.0.0",
6200
+
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
6201
+
"integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
6202
+
"license": "MIT",
6203
+
"dependencies": {
6204
+
"@types/unist": "^3.0.0"
6205
+
},
6206
+
"funding": {
6207
+
"type": "opencollective",
6208
+
"url": "https://opencollective.com/unified"
6209
+
}
6210
+
},
6211
+
"node_modules/unist-util-visit": {
6212
+
"version": "5.0.0",
6213
+
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
6214
+
"integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
6215
+
"license": "MIT",
6216
+
"dependencies": {
6217
+
"@types/unist": "^3.0.0",
6218
+
"unist-util-is": "^6.0.0",
6219
+
"unist-util-visit-parents": "^6.0.0"
6220
+
},
6221
+
"funding": {
6222
+
"type": "opencollective",
6223
+
"url": "https://opencollective.com/unified"
6224
+
}
6225
+
},
6226
+
"node_modules/unist-util-visit-parents": {
6227
+
"version": "6.0.1",
6228
+
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
6229
+
"integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
6230
+
"license": "MIT",
6231
+
"dependencies": {
6232
+
"@types/unist": "^3.0.0",
6233
+
"unist-util-is": "^6.0.0"
6234
+
},
6235
+
"funding": {
6236
+
"type": "opencollective",
6237
+
"url": "https://opencollective.com/unified"
6238
+
}
6239
+
},
5755
6240
"node_modules/unpipe": {
5756
6241
"version": "1.0.0",
5757
6242
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
···
5796
6281
"license": "MIT",
5797
6282
"engines": {
5798
6283
"node": ">= 0.8"
6284
+
}
6285
+
},
6286
+
"node_modules/vfile": {
6287
+
"version": "6.0.3",
6288
+
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
6289
+
"integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
6290
+
"license": "MIT",
6291
+
"dependencies": {
6292
+
"@types/unist": "^3.0.0",
6293
+
"vfile-message": "^4.0.0"
6294
+
},
6295
+
"funding": {
6296
+
"type": "opencollective",
6297
+
"url": "https://opencollective.com/unified"
6298
+
}
6299
+
},
6300
+
"node_modules/vfile-message": {
6301
+
"version": "4.0.2",
6302
+
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
6303
+
"integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
6304
+
"license": "MIT",
6305
+
"dependencies": {
6306
+
"@types/unist": "^3.0.0",
6307
+
"unist-util-stringify-position": "^4.0.0"
6308
+
},
6309
+
"funding": {
6310
+
"type": "opencollective",
6311
+
"url": "https://opencollective.com/unified"
5799
6312
}
5800
6313
},
5801
6314
"node_modules/void-elements": {
···
6003
6516
"license": "MIT",
6004
6517
"funding": {
6005
6518
"url": "https://github.com/sponsors/colinhacks"
6519
+
}
6520
+
},
6521
+
"node_modules/zwitch": {
6522
+
"version": "2.0.4",
6523
+
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
6524
+
"integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
6525
+
"license": "MIT",
6526
+
"funding": {
6527
+
"type": "github",
6528
+
"url": "https://github.com/sponsors/wooorm"
6006
6529
}
6007
6530
}
6008
6531
}
+5
package.json
+5
package.json
···
30
30
"multiformats": "^9.9.0",
31
31
"pino": "^9.3.2",
32
32
"pug": "^3.0.3",
33
+
"shiki": "^3.4.2",
33
34
"uhtml": "^4.5.9"
34
35
},
35
36
"devDependencies": {
···
49
50
"!src/**/__tests__/**",
50
51
"!src/**/*.test.*"
51
52
],
53
+
"loader": {
54
+
".pug": "copy",
55
+
".woff2": "copy"
56
+
},
52
57
"splitting": false,
53
58
"sourcemap": true,
54
59
"clean": true
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",
+48
-41
src/auth/storage.ts
+48
-41
src/auth/storage.ts
···
1
1
import type {
2
-
NodeSavedSession,
3
-
NodeSavedSessionStore,
4
-
NodeSavedState,
5
-
NodeSavedStateStore,
6
-
} from '@atproto/oauth-client-node'
7
-
import type { Database } from '#/db'
2
+
NodeSavedSession,
3
+
NodeSavedSessionStore,
4
+
NodeSavedState,
5
+
NodeSavedStateStore,
6
+
} from "@atproto/oauth-client-node";
7
+
import type { Database } from "#/db";
8
8
9
9
export class StateStore implements NodeSavedStateStore {
10
-
constructor(private db: Database) {}
11
-
async get(key: string): Promise<NodeSavedState | undefined> {
12
-
const result = await this.db.selectFrom('auth_state').selectAll().where('key', '=', key).executeTakeFirst()
13
-
if (!result) return
14
-
return JSON.parse(result.state) as NodeSavedState
15
-
}
16
-
async set(key: string, val: NodeSavedState) {
17
-
const state = JSON.stringify(val)
18
-
await this.db
19
-
.insertInto('auth_state')
20
-
.values({ key, state })
21
-
.onConflict((oc) => oc.doUpdateSet({ state }))
22
-
.execute()
23
-
}
24
-
async del(key: string) {
25
-
await this.db.deleteFrom('auth_state').where('key', '=', key).execute()
26
-
}
10
+
constructor(private db: Database) {}
11
+
async get(key: string): Promise<NodeSavedState | undefined> {
12
+
const result = await this.db
13
+
.selectFrom("auth_state")
14
+
.selectAll()
15
+
.where("key", "=", key)
16
+
.executeTakeFirst();
17
+
if (!result) return;
18
+
return JSON.parse(result.state) as NodeSavedState;
19
+
}
20
+
async set(key: string, val: NodeSavedState) {
21
+
const state = JSON.stringify(val);
22
+
await this.db
23
+
.insertInto("auth_state")
24
+
.values({ key, state })
25
+
.onConflict((oc) => oc.doUpdateSet({ state }))
26
+
.execute();
27
+
}
28
+
async del(key: string) {
29
+
await this.db.deleteFrom("auth_state").where("key", "=", key).execute();
30
+
}
27
31
}
28
32
29
33
export class SessionStore implements NodeSavedSessionStore {
30
-
constructor(private db: Database) {}
31
-
async get(key: string): Promise<NodeSavedSession | undefined> {
32
-
const result = await this.db.selectFrom('auth_session').selectAll().where('key', '=', key).executeTakeFirst()
33
-
if (!result) return
34
-
return JSON.parse(result.session) as NodeSavedSession
35
-
}
36
-
async set(key: string, val: NodeSavedSession) {
37
-
const session = JSON.stringify(val)
38
-
await this.db
39
-
.insertInto('auth_session')
40
-
.values({ key, session })
41
-
.onConflict((oc) => oc.doUpdateSet({ session }))
42
-
.execute()
43
-
}
44
-
async del(key: string) {
45
-
await this.db.deleteFrom('auth_session').where('key', '=', key).execute()
46
-
}
34
+
constructor(private db: Database) {}
35
+
async get(key: string): Promise<NodeSavedSession | undefined> {
36
+
const result = await this.db
37
+
.selectFrom("auth_session")
38
+
.selectAll()
39
+
.where("key", "=", key)
40
+
.executeTakeFirst();
41
+
if (!result) return;
42
+
return JSON.parse(result.session) as NodeSavedSession;
43
+
}
44
+
async set(key: string, val: NodeSavedSession) {
45
+
const session = JSON.stringify(val);
46
+
await this.db
47
+
.insertInto("auth_session")
48
+
.values({ key, session })
49
+
.onConflict((oc) => oc.doUpdateSet({ session }))
50
+
.execute();
51
+
}
52
+
async del(key: string) {
53
+
await this.db.deleteFrom("auth_session").where("key", "=", key).execute();
54
+
}
47
55
}
48
-
+31
src/db.ts
+31
src/db.ts
···
1
1
import SqliteDb from "better-sqlite3";
2
2
import { randomBytes } from "crypto";
3
+
import e from "express";
3
4
4
5
import {
5
6
Kysely,
···
12
13
13
14
export type DatabaseSchema = {
14
15
paste: Paste;
16
+
comment: Comment;
15
17
auth_state: AuthState;
16
18
auth_session: AuthSession;
17
19
};
···
19
21
export type Paste = {
20
22
uri: string;
21
23
authorDid: string;
24
+
shortUrl: string;
22
25
code: string;
23
26
lang: string;
24
27
title: string;
···
36
39
state: AuthStateJson;
37
40
};
38
41
42
+
export type Comment = {
43
+
uri: string;
44
+
authorDid: string;
45
+
body: string;
46
+
createdAt: string;
47
+
indexedAt: string;
48
+
pasteUri: string;
49
+
pasteCid: string;
50
+
};
51
+
39
52
type AuthSessionJson = string;
40
53
type AuthStateJson = string;
41
54
···
83
96
await db.schema.dropTable("auth_state").execute();
84
97
await db.schema.dropTable("auth_session").execute();
85
98
await db.schema.dropTable("paste").execute();
99
+
},
100
+
};
101
+
102
+
migrations["002"] = {
103
+
async up(db: Kysely<unknown>) {
104
+
await db.schema
105
+
.createTable("comment")
106
+
.addColumn("uri", "varchar", (col) => col.primaryKey())
107
+
.addColumn("authorDid", "varchar", (col) => col.notNull())
108
+
.addColumn("body", "varchar", (col) => col.notNull())
109
+
.addColumn("createdAt", "varchar", (col) => col.notNull())
110
+
.addColumn("indexedAt", "varchar", (col) => col.notNull())
111
+
.addColumn("pasteUri", "varchar", (col) => col.notNull())
112
+
.addColumn("pasteCid", "varchar", (col) => col.notNull())
113
+
.execute();
114
+
},
115
+
async down(db: Kysely<unknown>) {
116
+
await db.schema.dropTable("comments").execute();
86
117
},
87
118
};
88
119
+30
-32
src/id-resolver.ts
+30
-32
src/id-resolver.ts
···
1
-
import { IdResolver, MemoryCache } from '@atproto/identity'
2
-
3
-
const HOUR = 60e3 * 60
4
-
const DAY = HOUR * 24
1
+
import { IdResolver, MemoryCache } from "@atproto/identity";
5
2
3
+
const HOUR = 60e3 * 60;
4
+
const DAY = HOUR * 24;
6
5
7
6
export function createIdResolver() {
8
-
return new IdResolver({
9
-
didCache: new MemoryCache(HOUR, DAY),
10
-
})
7
+
return new IdResolver({
8
+
didCache: new MemoryCache(HOUR, DAY),
9
+
});
11
10
}
12
11
13
12
export interface BidirectionalResolver {
14
-
resolveDidToHandle(did: string): Promise<string>
15
-
resolveDidsToHandles(dids: string[]): Promise<Record<string, string>>
13
+
resolveDidToHandle(did: string): Promise<string>;
14
+
resolveDidsToHandles(dids: string[]): Promise<Record<string, string>>;
16
15
}
17
16
18
17
export function createBidirectionalResolver(resolver: IdResolver) {
19
-
return {
20
-
async resolveDidToHandle(did: string): Promise<string> {
21
-
const didDoc = await resolver.did.resolveAtprotoData(did)
22
-
const resolvedHandle = await resolver.handle.resolve(didDoc.handle)
23
-
if (resolvedHandle === did) {
24
-
return didDoc.handle
25
-
}
26
-
return did
27
-
},
18
+
return {
19
+
async resolveDidToHandle(did: string): Promise<string> {
20
+
const didDoc = await resolver.did.resolveAtprotoData(did);
21
+
const resolvedHandle = await resolver.handle.resolve(didDoc.handle);
22
+
if (resolvedHandle === did) {
23
+
return didDoc.handle;
24
+
}
25
+
return did;
26
+
},
28
27
29
-
async resolveDidsToHandles(
30
-
dids: string[]
31
-
): Promise<Record<string, string>> {
32
-
const didHandleMap: Record<string, string> = {}
33
-
const resolves = await Promise.all(
34
-
dids.map((did) => this.resolveDidToHandle(did).catch((_) => did))
35
-
)
36
-
for (let i = 0; i < dids.length; i++) {
37
-
didHandleMap[dids[i]] = resolves[i]
38
-
}
39
-
return didHandleMap
40
-
},
41
-
}
28
+
async resolveDidsToHandles(
29
+
dids: string[],
30
+
): Promise<Record<string, string>> {
31
+
const didHandleMap: Record<string, string> = {};
32
+
const resolves = await Promise.all(
33
+
dids.map((did) => this.resolveDidToHandle(did).catch((_) => did)),
34
+
);
35
+
for (let i = 0; i < dids.length; i++) {
36
+
didHandleMap[dids[i]] = resolves[i];
37
+
}
38
+
return didHandleMap;
39
+
},
40
+
};
42
41
}
43
-
+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
}
+40
-8
src/ingester.ts
+40
-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 { newShortUrl } from "#/db";
6
-
import * as Paste from "#/lexicons/types/ovh/plonk/paste";
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
) {
24
-
// Store the status in our SQLite
25
-
const short_url = await newShortUrl(db);
26
24
await db
27
25
.insertInto("paste")
28
26
.values({
29
27
uri: evt.uri.toString(),
30
-
shortUrl,
28
+
shortUrl: record.shortUrl,
31
29
authorDid: evt.did,
32
30
code: record.code,
33
31
lang: record.lang,
···
44
42
}),
45
43
)
46
44
.execute();
45
+
} else if (
46
+
evt.collection === "li.plonk.comment" &&
47
+
Comment.isRecord(record) &&
48
+
Comment.validateRecord(record).success
49
+
) {
50
+
await db
51
+
.insertInto("comment")
52
+
.values({
53
+
uri: evt.uri.toString(),
54
+
authorDid: evt.did,
55
+
body: record.content,
56
+
pasteUri: record.post.uri,
57
+
pasteCid: record.post.cid,
58
+
createdAt: record.createdAt,
59
+
indexedAt: now.toISOString(),
60
+
})
61
+
.onConflict((oc) =>
62
+
oc.column("uri").doUpdateSet({
63
+
body: record.content,
64
+
pasteUri: record.post.uri,
65
+
pasteCid: record.post.cid,
66
+
indexedAt: now.toISOString(),
67
+
}),
68
+
)
69
+
.execute();
47
70
}
48
71
} else if (
49
72
evt.event === "delete" &&
50
-
evt.collection === "ovh.plonk.paste"
73
+
evt.collection === "li.plonk.paste"
51
74
) {
52
75
// Remove the status from our SQLite
53
76
await db
54
77
.deleteFrom("paste")
55
78
.where("uri", "=", evt.uri.toString())
56
79
.execute();
80
+
} else if (
81
+
evt.event === "delete" &&
82
+
evt.collection === "li.plonk.comment"
83
+
) {
84
+
// Remove the status from our SQLite
85
+
await db
86
+
.deleteFrom("comment")
87
+
.where("uri", "=", evt.uri.toString())
88
+
.execute();
57
89
}
58
90
},
59
91
onError: (err) => {
60
92
logger.error({ err }, "error on firehose ingestion");
61
93
},
62
-
filterCollections: ["ovh.plonk.paste"],
94
+
filterCollections: ["li.plonk.paste"],
63
95
excludeIdentity: true,
64
96
excludeAccount: true,
65
97
});
+113
-89
src/lexicons/index.ts
+113
-89
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'
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";
12
14
13
15
export function createServer(options?: XrpcOptions): Server {
14
-
return new Server(options)
16
+
return new Server(options);
15
17
}
16
18
17
19
export class Server {
18
-
xrpc: XrpcServer
19
-
ovh: OvhNS
20
-
app: AppNS
21
-
com: ComNS
20
+
xrpc: XrpcServer;
21
+
li: LiNS;
22
+
com: ComNS;
23
+
app: AppNS;
22
24
23
-
constructor(options?: XrpcOptions) {
24
-
this.xrpc = createXrpcServer(schemas, options)
25
-
this.ovh = new OvhNS(this)
26
-
this.app = new AppNS(this)
27
-
this.com = new ComNS(this)
28
-
}
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
+
}
29
31
}
30
32
31
-
export class OvhNS {
32
-
_server: Server
33
-
plonk: OvhPlonkNS
33
+
export class LiNS {
34
+
_server: Server;
35
+
plonk: LiPlonkNS;
34
36
35
-
constructor(server: Server) {
36
-
this._server = server
37
-
this.plonk = new OvhPlonkNS(server)
38
-
}
37
+
constructor(server: Server) {
38
+
this._server = server;
39
+
this.plonk = new LiPlonkNS(server);
40
+
}
39
41
}
40
42
41
-
export class OvhPlonkNS {
42
-
_server: Server
43
+
export class LiPlonkNS {
44
+
_server: Server;
43
45
44
-
constructor(server: Server) {
45
-
this._server = server
46
-
}
46
+
constructor(server: Server) {
47
+
this._server = server;
48
+
}
47
49
}
48
50
49
-
export class AppNS {
50
-
_server: Server
51
-
bsky: AppBskyNS
51
+
export class ComNS {
52
+
_server: Server;
53
+
atproto: ComAtprotoNS;
52
54
53
-
constructor(server: Server) {
54
-
this._server = server
55
-
this.bsky = new AppBskyNS(server)
56
-
}
55
+
constructor(server: Server) {
56
+
this._server = server;
57
+
this.atproto = new ComAtprotoNS(server);
58
+
}
57
59
}
58
60
59
-
export class AppBskyNS {
60
-
_server: Server
61
-
actor: AppBskyActorNS
61
+
export class ComAtprotoNS {
62
+
_server: Server;
63
+
repo: ComAtprotoRepoNS;
62
64
63
-
constructor(server: Server) {
64
-
this._server = server
65
-
this.actor = new AppBskyActorNS(server)
66
-
}
65
+
constructor(server: Server) {
66
+
this._server = server;
67
+
this.repo = new ComAtprotoRepoNS(server);
68
+
}
67
69
}
68
70
69
-
export class AppBskyActorNS {
70
-
_server: Server
71
+
export class ComAtprotoRepoNS {
72
+
_server: Server;
71
73
72
-
constructor(server: Server) {
73
-
this._server = server
74
-
}
74
+
constructor(server: Server) {
75
+
this._server = server;
76
+
}
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
+
}
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
+
}
75
99
}
76
100
77
-
export class ComNS {
78
-
_server: Server
79
-
atproto: ComAtprotoNS
101
+
export class AppNS {
102
+
_server: Server;
103
+
bsky: AppBskyNS;
80
104
81
-
constructor(server: Server) {
82
-
this._server = server
83
-
this.atproto = new ComAtprotoNS(server)
84
-
}
105
+
constructor(server: Server) {
106
+
this._server = server;
107
+
this.bsky = new AppBskyNS(server);
108
+
}
85
109
}
86
110
87
-
export class ComAtprotoNS {
88
-
_server: Server
89
-
repo: ComAtprotoRepoNS
111
+
export class AppBskyNS {
112
+
_server: Server;
113
+
actor: AppBskyActorNS;
90
114
91
-
constructor(server: Server) {
92
-
this._server = server
93
-
this.repo = new ComAtprotoRepoNS(server)
94
-
}
115
+
constructor(server: Server) {
116
+
this._server = server;
117
+
this.actor = new AppBskyActorNS(server);
118
+
}
95
119
}
96
120
97
-
export class ComAtprotoRepoNS {
98
-
_server: Server
121
+
export class AppBskyActorNS {
122
+
_server: Server;
99
123
100
-
constructor(server: Server) {
101
-
this._server = server
102
-
}
124
+
constructor(server: Server) {
125
+
this._server = server;
126
+
}
103
127
}
104
128
105
129
type SharedRateLimitOpts<T> = {
106
-
name: string
107
-
calcKey?: (ctx: T) => string
108
-
calcPoints?: (ctx: T) => number
109
-
}
130
+
name: string;
131
+
calcKey?: (ctx: T) => string;
132
+
calcPoints?: (ctx: T) => number;
133
+
};
110
134
type RouteRateLimitOpts<T> = {
111
-
durationMs: number
112
-
points: number
113
-
calcKey?: (ctx: T) => string
114
-
calcPoints?: (ctx: T) => number
115
-
}
116
-
type HandlerOpts = { blobLimit?: number }
117
-
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>;
118
142
type ConfigOf<Auth, Handler, ReqCtx> =
119
-
| Handler
120
-
| {
121
-
auth?: Auth
122
-
opts?: HandlerOpts
123
-
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]
124
-
handler: Handler
125
-
}
143
+
| Handler
144
+
| {
145
+
auth?: Auth;
146
+
opts?: HandlerOpts;
147
+
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[];
148
+
handler: Handler;
149
+
};
126
150
type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract<
127
-
Awaited<ReturnType<AV>>,
128
-
{ credentials: unknown }
129
-
>
151
+
Awaited<ReturnType<AV>>,
152
+
{ credentials: unknown }
153
+
>;
+491
-302
src/lexicons/lexicons.ts
+491
-302
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
-
ComAtprotoLabelDefs: {
8
-
lexicon: 1,
9
-
id: 'com.atproto.label.defs',
10
-
defs: {
11
-
label: {
12
-
type: 'object',
13
-
description:
14
-
'Metadata tag on an atproto resource (eg, repo or record).',
15
-
required: ['src', 'uri', 'val', 'cts'],
16
-
properties: {
17
-
ver: {
18
-
type: 'integer',
19
-
description: 'The AT Protocol version of the label object.',
20
-
},
21
-
src: {
22
-
type: 'string',
23
-
format: 'did',
24
-
description: 'DID of the actor who created this label.',
25
-
},
26
-
uri: {
27
-
type: 'string',
28
-
format: 'uri',
29
-
description:
30
-
'AT URI of the record, repository (account), or other resource that this label applies to.',
31
-
},
32
-
cid: {
33
-
type: 'string',
34
-
format: 'cid',
35
-
description:
36
-
"Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
37
-
},
38
-
val: {
39
-
type: 'string',
40
-
maxLength: 128,
41
-
description:
42
-
'The short string name of the value or type of this label.',
43
-
},
44
-
neg: {
45
-
type: 'boolean',
46
-
description:
47
-
'If true, this is a negation label, overwriting a previous label.',
48
-
},
49
-
cts: {
50
-
type: 'string',
51
-
format: 'datetime',
52
-
description: 'Timestamp when this label was created.',
53
-
},
54
-
exp: {
55
-
type: 'string',
56
-
format: 'datetime',
57
-
description:
58
-
'Timestamp at which this label expires (no longer applies).',
59
-
},
60
-
sig: {
61
-
type: 'bytes',
62
-
description: 'Signature of dag-cbor encoded label.',
63
-
},
64
-
},
65
-
},
66
-
selfLabels: {
67
-
type: 'object',
68
-
description:
69
-
'Metadata tags on an atproto record, published by the author within the record.',
70
-
required: ['values'],
71
-
properties: {
72
-
values: {
73
-
type: 'array',
74
-
items: {
75
-
type: 'ref',
76
-
ref: 'lex:com.atproto.label.defs#selfLabel',
77
-
},
78
-
maxLength: 10,
79
-
},
80
-
},
81
-
},
82
-
selfLabel: {
83
-
type: 'object',
84
-
description:
85
-
'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.',
86
-
required: ['val'],
87
-
properties: {
88
-
val: {
89
-
type: 'string',
90
-
maxLength: 128,
91
-
description:
92
-
'The short string name of the value or type of this label.',
93
-
},
94
-
},
95
-
},
96
-
labelValueDefinition: {
97
-
type: 'object',
98
-
description:
99
-
'Declares a label value and its expected interpretations and behaviors.',
100
-
required: ['identifier', 'severity', 'blurs', 'locales'],
101
-
properties: {
102
-
identifier: {
103
-
type: 'string',
104
-
description:
105
-
"The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
106
-
maxLength: 100,
107
-
maxGraphemes: 100,
108
-
},
109
-
severity: {
110
-
type: 'string',
111
-
description:
112
-
"How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
113
-
knownValues: ['inform', 'alert', 'none'],
114
-
},
115
-
blurs: {
116
-
type: 'string',
117
-
description:
118
-
"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.",
119
-
knownValues: ['content', 'media', 'none'],
120
-
},
121
-
defaultSetting: {
122
-
type: 'string',
123
-
description: 'The default setting for this label.',
124
-
knownValues: ['ignore', 'warn', 'hide'],
125
-
default: 'warn',
126
-
},
127
-
adultOnly: {
128
-
type: 'boolean',
129
-
description:
130
-
'Does the user need to have adult content enabled in order to configure this label?',
131
-
},
132
-
locales: {
133
-
type: 'array',
134
-
items: {
135
-
type: 'ref',
136
-
ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings',
137
-
},
138
-
},
139
-
},
140
-
},
141
-
labelValueDefinitionStrings: {
142
-
type: 'object',
143
-
description:
144
-
'Strings which describe the label in the UI, localized into a specific language.',
145
-
required: ['lang', 'name', 'description'],
146
-
properties: {
147
-
lang: {
148
-
type: 'string',
149
-
description:
150
-
'The code of the language these strings are written in.',
151
-
format: 'language',
152
-
},
153
-
name: {
154
-
type: 'string',
155
-
description: 'A short human-readable name for the label.',
156
-
maxGraphemes: 64,
157
-
maxLength: 640,
158
-
},
159
-
description: {
160
-
type: 'string',
161
-
description:
162
-
'A longer description of what the label means and why it might be applied.',
163
-
maxGraphemes: 10000,
164
-
maxLength: 100000,
165
-
},
166
-
},
167
-
},
168
-
labelValue: {
169
-
type: 'string',
170
-
knownValues: [
171
-
'!hide',
172
-
'!no-promote',
173
-
'!warn',
174
-
'!no-unauthenticated',
175
-
'dmca-violation',
176
-
'doxxing',
177
-
'porn',
178
-
'sexual',
179
-
'nudity',
180
-
'nsfl',
181
-
'gore',
182
-
],
183
-
},
184
-
},
185
-
},
186
-
OvhPlonkPaste: {
187
-
lexicon: 1,
188
-
id: 'ovh.plonk.paste',
189
-
defs: {
190
-
main: {
191
-
type: 'record',
192
-
key: 'tid',
193
-
record: {
194
-
type: 'object',
195
-
required: ['code', 'lang', 'title', 'createdAt'],
196
-
properties: {
197
-
code: {
198
-
type: 'string',
199
-
minLength: 1,
200
-
maxGraphemes: 65536,
201
-
maxLength: 65536,
202
-
},
203
-
lang: {
204
-
type: 'string',
205
-
minLength: 1,
206
-
maxGraphemes: 20,
207
-
maxLength: 20,
208
-
},
209
-
title: {
210
-
type: 'string',
211
-
minLength: 1,
212
-
maxGraphemes: 100,
213
-
maxLength: 100,
214
-
},
215
-
createdAt: {
216
-
type: 'string',
217
-
format: 'datetime',
218
-
},
219
-
},
220
-
},
221
-
},
222
-
},
223
-
},
224
-
AppBskyActorProfile: {
225
-
lexicon: 1,
226
-
id: 'app.bsky.actor.profile',
227
-
defs: {
228
-
main: {
229
-
type: 'record',
230
-
description: 'A declaration of a Bluesky account profile.',
231
-
key: 'literal:self',
232
-
record: {
233
-
type: 'object',
234
-
properties: {
235
-
displayName: {
236
-
type: 'string',
237
-
maxGraphemes: 64,
238
-
maxLength: 640,
239
-
},
240
-
description: {
241
-
type: 'string',
242
-
description: 'Free-form profile description text.',
243
-
maxGraphemes: 256,
244
-
maxLength: 2560,
245
-
},
246
-
avatar: {
247
-
type: 'blob',
248
-
description:
249
-
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
250
-
accept: ['image/png', 'image/jpeg'],
251
-
maxSize: 1000000,
252
-
},
253
-
banner: {
254
-
type: 'blob',
255
-
description:
256
-
'Larger horizontal image to display behind profile view.',
257
-
accept: ['image/png', 'image/jpeg'],
258
-
maxSize: 1000000,
259
-
},
260
-
labels: {
261
-
type: 'union',
262
-
description:
263
-
'Self-label values, specific to the Bluesky application, on the overall account.',
264
-
refs: ['lex:com.atproto.label.defs#selfLabels'],
265
-
},
266
-
joinedViaStarterPack: {
267
-
type: 'ref',
268
-
ref: 'lex:com.atproto.repo.strongRef',
269
-
},
270
-
createdAt: {
271
-
type: 'string',
272
-
format: 'datetime',
273
-
},
274
-
},
275
-
},
276
-
},
277
-
},
278
-
},
279
-
ComAtprotoRepoStrongRef: {
280
-
lexicon: 1,
281
-
id: 'com.atproto.repo.strongRef',
282
-
description: 'A URI with a content-hash fingerprint.',
283
-
defs: {
284
-
main: {
285
-
type: 'object',
286
-
required: ['uri', 'cid'],
287
-
properties: {
288
-
uri: {
289
-
type: 'string',
290
-
format: 'at-uri',
291
-
},
292
-
cid: {
293
-
type: 'string',
294
-
format: 'cid',
295
-
},
296
-
},
297
-
},
298
-
},
299
-
},
300
-
}
301
-
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
302
-
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);
303
489
export const ids = {
304
-
ComAtprotoLabelDefs: 'com.atproto.label.defs',
305
-
OvhPlonkPaste: 'ovh.plonk.paste',
306
-
AppBskyActorProfile: 'app.bsky.actor.profile',
307
-
ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef',
308
-
}
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 & {});
+55
src/lexicons/types/com/atproto/repo/getRecord.ts
+55
src/lexicons/types/com/atproto/repo/getRecord.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
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";
10
+
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;
20
+
}
21
+
22
+
export type InputSchema = undefined;
23
+
24
+
export interface OutputSchema {
25
+
uri: string;
26
+
cid?: string;
27
+
value: {};
28
+
[k: string]: unknown;
29
+
}
30
+
31
+
export type HandlerInput = undefined;
32
+
33
+
export interface HandlerSuccess {
34
+
encoding: "application/json";
35
+
body: OutputSchema;
36
+
headers?: { [key: string]: string };
37
+
}
38
+
39
+
export interface HandlerError {
40
+
status: number;
41
+
message?: string;
42
+
error?: "RecordNotFound";
43
+
}
44
+
45
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
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
+
};
53
+
export type Handler<HA extends HandlerAuth = never> = (
54
+
ctx: HandlerReqCtx<HA>,
55
+
) => Promise<HandlerOutput> | HandlerOutput;
+77
src/lexicons/types/com/atproto/repo/listRecords.ts
+77
src/lexicons/types/com/atproto/repo/listRecords.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
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";
10
+
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;
25
+
}
26
+
27
+
export type InputSchema = undefined;
28
+
29
+
export interface OutputSchema {
30
+
cursor?: string;
31
+
records: Record[];
32
+
[k: string]: unknown;
33
+
}
34
+
35
+
export type HandlerInput = undefined;
36
+
37
+
export interface HandlerSuccess {
38
+
encoding: "application/json";
39
+
body: OutputSchema;
40
+
headers?: { [key: string]: string };
41
+
}
42
+
43
+
export interface HandlerError {
44
+
status: number;
45
+
message?: string;
46
+
}
47
+
48
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
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
+
};
56
+
export type Handler<HA extends HandlerAuth = never> = (
57
+
ctx: HandlerReqCtx<HA>,
58
+
) => Promise<HandlerOutput> | HandlerOutput;
59
+
60
+
export interface Record {
61
+
uri: string;
62
+
cid: string;
63
+
value: {};
64
+
[k: string]: unknown;
65
+
}
66
+
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
+
);
73
+
}
74
+
75
+
export function validateRecord(v: unknown): ValidationResult {
76
+
return lexicons.validate("com.atproto.repo.listRecords#record", v);
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
+
}
-27
src/lexicons/types/ovh/plonk/paste.ts
-27
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
-
lang: string
12
-
title: string
13
-
createdAt: string
14
-
[k: string]: unknown
15
-
}
16
-
17
-
export function isRecord(v: unknown): v is Record {
18
-
return (
19
-
isObj(v) &&
20
-
hasProp(v, '$type') &&
21
-
(v.$type === 'ovh.plonk.paste#main' || v.$type === 'ovh.plonk.paste')
22
-
)
23
-
}
24
-
25
-
export function validateRecord(v: unknown): ValidationResult {
26
-
return lexicons.validate('ovh.plonk.paste#main', v)
27
-
}
+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
}
+48
src/lib/highlight.ts
+48
src/lib/highlight.ts
···
1
+
import { createHighlighter, HighlighterGeneric, BundledLanguage, BundledTheme } from 'shiki'
2
+
import type { Element } from 'hast'
3
+
4
+
let highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | null = null
5
+
6
+
export async function getHighlighterInstance() {
7
+
if (!highlighter) {
8
+
highlighter = await createHighlighter({
9
+
themes: ['catppuccin-latte', 'catppuccin-mocha'],
10
+
langs: [
11
+
'javascript', 'typescript', 'python', 'java', 'rust',
12
+
'go', 'c', 'cpp', 'csharp', 'php', 'ruby', 'cobol',
13
+
'plaintext', 'txt', 'bash', 'nix'
14
+
]
15
+
})
16
+
}
17
+
return highlighter
18
+
}
19
+
20
+
export async function highlightCode(code: string, lang: string) {
21
+
const highlighter = await getHighlighterInstance()
22
+
23
+
try {
24
+
return highlighter.codeToHtml(code, {
25
+
lang: lang,
26
+
themes: {
27
+
light: 'catppuccin-latte',
28
+
dark: 'catppuccin-mocha'
29
+
},
30
+
transformers: [
31
+
{
32
+
line(node: Element, line: number) {
33
+
node.properties['code-line'] = line
34
+
}
35
+
}
36
+
]
37
+
})
38
+
} catch (error) {
39
+
console.warn(`Language "${lang}" not supported by Shiki, falling back to plaintext`)
40
+
return highlighter.codeToHtml(code, {
41
+
lang: 'txt',
42
+
themes: {
43
+
light: 'catppuccin-latte',
44
+
dark: 'catppuccin-mocha'
45
+
}
46
+
})
47
+
}
48
+
}
+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
+
+6
-5
src/mixins/post.pug
+6
-5
src/mixins/post.pug
···
1
-
mixin post(paste, didHandleMap)
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
8
-
a(href=`/u/${encodeURIComponent(paste.authorDid)}`)
9
-
| @#{didHandleMap[paste.authorDid]}
8
+
a(href=`/u/${did}`)
9
+
| @#{handle}
10
10
| ยท
11
11
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
12
12
| ยท
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
+142
-6
src/public/styles.css
+142
-6
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
+
}
7
+
8
+
@media (prefers-color-scheme: dark) {
9
+
.shiki,
10
+
.shiki span {
11
+
color: var(--shiki-dark) !important;
12
+
background-color: var(--shiki-dark-bg) !important;
13
+
font-style: var(--shiki-dark-font-style) !important;
14
+
font-weight: var(--shiki-dark-font-weight) !important;
15
+
text-decoration: var(--shiki-dark-text-decoration) !important;
16
+
}
17
+
}
2
18
3
19
:root {
4
20
/* Light mode colors */
···
28
44
}
29
45
30
46
* {
31
-
font-family: 'IBM Plex Mono', monospace;
47
+
font-family: 'NerdIosevka', monospace;
48
+
font-size: 0.9rem;
32
49
}
33
50
34
51
body {
···
52
69
}
53
70
54
71
pre {
55
-
background-color: var(--bg-color-muted);
56
72
padding: 1rem;
57
73
overflow-x: auto;
58
74
}
59
75
76
+
.comment-body {
77
+
background-color: var(--bg-color);
78
+
padding: 0;
79
+
margin-top: 0.1rem;
80
+
}
81
+
82
+
.comment-info {
83
+
margin-bottom: 0;
84
+
}
85
+
60
86
input, textarea, select, button {
61
87
border: none;
62
88
padding: 1rem;
···
93
119
hr {
94
120
border: none;
95
121
border-top: 1px solid var(--bg-color-muted);
96
-
padding: 1rem;
97
122
}
98
123
99
124
.post-form {
···
127
152
align-self: flex-end;
128
153
}
129
154
130
-
.timeline {
155
+
.post-link {
156
+
color: var(--text-color);
157
+
text-decoration: none;
158
+
}
159
+
.post-link:hover {
160
+
text-decoration: underline;
161
+
}
162
+
.post-link:visited {
163
+
color: var(--text-color-muted);
164
+
}
165
+
166
+
.post-info {
167
+
margin-top: 0;
168
+
}
169
+
170
+
.post-info, .comment-info {
171
+
color: var(--text-color-muted);
172
+
}
173
+
.post-info a, .comment-info a {
174
+
color: var(--text-color-muted);
175
+
text-decoration: none;
176
+
}
177
+
.post-info a:visited, .comment-info a:visited {
178
+
color: var(--text-color-muted);
179
+
}
180
+
.post-info a:hover, .comment-info a:hover {
181
+
text-decoration: underline;
182
+
}
183
+
184
+
.timeline, .comments {
131
185
display: flex;
132
186
flex-direction: column;
133
187
gap: 1rem;
188
+
padding-bottom: 1rem;
134
189
}
135
190
136
191
.login-input-title {
···
141
196
flex: 1
142
197
}
143
198
144
-
.header {
199
+
.header, .footer {
145
200
display: flex;
146
201
flex-direction: row;
147
202
justify-content: space-between;
148
203
align-items: center;
149
204
}
205
+
206
+
select {
207
+
-webkit-appearance: none;
208
+
-moz-appearance: none;
209
+
text-indent: 1px;
210
+
text-overflow: '';
211
+
}
212
+
213
+
.code-line {
214
+
display: flex;
215
+
}
216
+
217
+
.code-line-num {
218
+
white-space: pre;
219
+
-webkit-user-select: none;
220
+
user-select: none;
221
+
margin-right: 0.4em;
222
+
padding: 0 0.4em 0 0.4em;
223
+
color: var(--text-color-muted);
224
+
text-align: right;
225
+
}
226
+
227
+
.code-line-content {
228
+
color: var(--text-color);
229
+
}
230
+
231
+
.header, .footer {
232
+
color: var(--text-color);
233
+
}
234
+
235
+
.header a, .header a:visited,
236
+
.footer a, .footer a:visited {
237
+
color: var(--link-color);
238
+
text-decoration: none;
239
+
}
240
+
241
+
.header a:hover,
242
+
.footer a:hover {
243
+
text-decoration: underline;
244
+
}
245
+
246
+
.highlighted-code {
247
+
margin: 1rem 0;
248
+
counter-reset: step;
249
+
counter-increment: step calc(var(--start, 1) - 1);
250
+
}
251
+
252
+
.highlighted-code pre {
253
+
padding: 1rem;
254
+
overflow-x: auto;
255
+
border-radius: 4px;
256
+
}
257
+
258
+
code .line::before {
259
+
content: counter(step);
260
+
counter-increment: step;
261
+
width: 1rem;
262
+
margin-right: 1.5rem;
263
+
display: inline-block;
264
+
text-align: right;
265
+
color: rgba(115,138,148,.4)
266
+
}
267
+
268
+
#copy-btn {
269
+
background: none;
270
+
border: none;
271
+
padding: 0;
272
+
color: var(--text-color-muted);
273
+
cursor: pointer;
274
+
text-decoration: none;
275
+
font-family: inherit;
276
+
font-size: inherit;
277
+
}
278
+
279
+
#copy-btn:hover {
280
+
text-decoration: underline;
281
+
}
282
+
283
+
#copy-btn:focus {
284
+
outline: 1px solid var(--accent);
285
+
}
+246
-66
src/routes.ts
+246
-66
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
10
import { newShortUrl } from "#/db";
12
11
13
-
import * as Paste from "#/lexicons/types/ovh/plonk/paste";
12
+
import * as Paste from "#/lexicons/types/li/plonk/paste";
13
+
import * as Comment from "#/lexicons/types/li/plonk/comment";
14
+
import { highlightCode } from "#/lib/highlight";
14
15
15
16
type Session = {
16
17
did: string;
17
18
};
18
19
20
+
async function getSession(
21
+
req: IncomingMessage,
22
+
res: ServerResponse<IncomingMessage>,
23
+
) {
24
+
return await getIronSession<Session>(req, res, {
25
+
cookieName: "plonk-id",
26
+
password: env.PLONK_COOKIE_SECRET,
27
+
cookieOptions: {
28
+
secure: env.PLONK_NODE_ENV === "production",
29
+
},
30
+
});
31
+
}
32
+
19
33
async function getSessionAgent(
20
34
req: IncomingMessage,
21
35
res: ServerResponse<IncomingMessage>,
22
36
ctx: Ctx,
23
37
) {
24
-
const session = await getIronSession<Session>(req, res, {
25
-
cookieName: "plonk-id",
26
-
password: env.COOKIE_SECRET,
27
-
});
38
+
const session = await getSession(req, res);
28
39
if (!session.did) return null;
29
40
try {
30
41
const oauthSession = await ctx.oauthClient.restore(session.did);
···
39
50
export const createRouter = (ctx: Ctx) => {
40
51
const router = express.Router();
41
52
42
-
// Static assets
43
-
router.use(
44
-
"/public",
45
-
express.static(path.join(__dirname, "pages", "public")),
46
-
);
47
-
53
+
router.use("/assets", express.static(path.join(__dirname, "assets")));
48
54
// OAuth metadata
49
55
router.get("/client-metadata.json", async (_req, res) => {
50
56
return res.json(ctx.oauthClient.clientMetadata);
···
54
60
const params = new URLSearchParams(req.originalUrl.split("?")[1]);
55
61
try {
56
62
const { session } = await ctx.oauthClient.callback(params);
57
-
const clientSession = await getIronSession<Session>(req, res, {
58
-
cookieName: "plonk-id",
59
-
password: env.COOKIE_SECRET,
60
-
});
61
-
ctx.logger.info(clientSession.did, "client session did");
63
+
const clientSession = await getSession(req, res);
62
64
//assert(!clientSession.did, "session already exists");
63
65
clientSession.did = session.did;
64
66
await clientSession.save();
···
95
97
});
96
98
97
99
router.get("/logout", async (req, res) => {
98
-
const session = await getIronSession<Session>(req, res, {
99
-
cookieName: "plonk-id",
100
-
password: env.COOKIE_SECRET,
101
-
});
100
+
const session = await getSession(req, res);
102
101
session.destroy();
103
102
return res.redirect("/");
104
103
});
···
107
106
const agent = await getSessionAgent(req, res, ctx);
108
107
const pastes = await ctx.db
109
108
.selectFrom("paste")
110
-
.selectAll()
111
-
.orderBy("indexedAt", "desc")
109
+
.leftJoin("comment", "comment.pasteUri", "paste.uri")
110
+
.select([
111
+
"paste.uri",
112
+
"paste.shortUrl",
113
+
"paste.authorDid",
114
+
"paste.code",
115
+
"paste.lang",
116
+
"paste.title",
117
+
"paste.createdAt",
118
+
"paste.indexedAt as pasteIndexedAt",
119
+
ctx.db.fn.count("comment.uri").as("commentCount")
120
+
])
121
+
.groupBy("paste.uri")
122
+
.orderBy("pasteIndexedAt", "desc")
112
123
.limit(25)
113
124
.execute();
114
125
115
126
// Map user DIDs to their domain-name handles
116
127
const didHandleMap = await ctx.resolver.resolveDidsToHandles(
117
-
pastes.map((s) => s.authorDid),
128
+
pastes.map((s) => s.authorDid).concat(agent ? [agent.assertDid] : []),
118
129
);
119
130
120
131
if (!agent) {
···
132
143
const { authorDid } = req.params;
133
144
const pastes = await ctx.db
134
145
.selectFrom("paste")
135
-
.selectAll()
136
-
.where("authorDid", "=", authorDid)
137
-
.orderBy("indexedAt", "desc")
146
+
.leftJoin("comment", "comment.pasteUri", "paste.uri")
147
+
.select([
148
+
"paste.uri",
149
+
"paste.shortUrl",
150
+
"paste.authorDid as pasteAuthorDid",
151
+
"paste.code",
152
+
"paste.lang",
153
+
"paste.title",
154
+
"paste.createdAt as pasteCreatedAt",
155
+
"paste.indexedAt as pasteIndexedAt",
156
+
ctx.db.fn.count("comment.uri").as("commentCount")
157
+
])
158
+
.groupBy("paste.uri")
159
+
.where("pasteAuthorDid", "=", authorDid)
160
+
.orderBy("pasteCreatedAt", "desc")
138
161
.execute();
139
-
let didHandleMap = {};
162
+
let didHandleMap: Record<string, string> = {};
140
163
didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid);
141
-
return res.render("user", { pastes, authorDid, didHandleMap });
164
+
const ownAgent = await getSessionAgent(req, res, ctx);
165
+
if (!ownAgent) {
166
+
return res.render("user", { pastes, authorDid, didHandleMap });
167
+
} else {
168
+
const ownDid = ownAgent.assertDid;
169
+
didHandleMap[ownDid] = await ctx.resolver.resolveDidToHandle(ownDid);
170
+
return res.render("user", { pastes, authorDid, ownDid, didHandleMap });
171
+
}
142
172
});
143
173
144
174
router.get("/p/:shortUrl", async (req, res) => {
145
175
const { shortUrl } = req.params;
146
176
const ret = await ctx.db
147
177
.selectFrom("paste")
178
+
.leftJoin("comment", "comment.pasteUri", "paste.uri")
179
+
.select([
180
+
"paste.uri as pasteUri",
181
+
"comment.pasteCid as pasteCid",
182
+
"paste.authorDid as pasteAuthorDid",
183
+
"paste.code as pasteCode",
184
+
"paste.lang as pasteLang",
185
+
"paste.title as pasteTitle",
186
+
"paste.createdAt as pasteCreatedAt",
187
+
"comment.uri as commentUri",
188
+
"comment.authorDid as commentAuthorDid",
189
+
"comment.body as commentBody",
190
+
"comment.createdAt as commentCreatedAt",
191
+
])
148
192
.where("shortUrl", "=", shortUrl)
149
-
.select(["authorDid", "uri"])
150
-
.executeTakeFirst();
151
-
if (!ret) {
152
-
return res.status(404);
153
-
}
154
-
const { authorDid: did, uri } = ret;
155
-
const handle = await ctx.resolver.resolveDidToHandle(did);
156
-
const resolver = new DidResolver({});
157
-
const didDocument = await resolver.resolve(did);
158
-
if (!didDocument) {
159
-
return res.status(404);
160
-
}
161
-
const pds = getPds(didDocument);
162
-
if (!pds) {
193
+
.execute();
194
+
if (ret.length === 0) {
163
195
return res.status(404);
164
196
}
165
-
const aturi = new AtUri(uri);
166
-
const url = new URL(`${pds}/xrpc/com.atproto.repo.getRecord`);
167
-
url.searchParams.set("repo", aturi.hostname);
168
-
url.searchParams.set("collection", aturi.collection);
169
-
url.searchParams.set("rkey", aturi.rkey);
197
+
const {
198
+
pasteAuthorDid,
199
+
pasteUri,
200
+
pasteCode,
201
+
pasteLang,
202
+
pasteTitle,
203
+
pasteCreatedAt,
204
+
} = ret[0];
205
+
let didHandleMap = await ctx.resolver.resolveDidsToHandles(
206
+
[ret[0].pasteAuthorDid].concat(
207
+
ret.flatMap((row) =>
208
+
row.commentAuthorDid ? [row.commentAuthorDid] : [],
209
+
),
210
+
),
211
+
);
170
212
171
-
const response = await fetch(url.toString());
213
+
const highlightedCode = await highlightCode(pasteCode, pasteLang);
172
214
173
-
if (!response.ok) {
174
-
return res.status(404);
175
-
}
215
+
const paste = {
216
+
uri: pasteUri,
217
+
code: pasteCode,
218
+
highlightedCode,
219
+
title: pasteTitle,
220
+
lang: pasteLang,
221
+
shortUrl,
222
+
createdAt: pasteCreatedAt,
223
+
authorDid: pasteAuthorDid,
224
+
};
176
225
177
-
const pasteRecord = await response.json();
178
-
const paste =
179
-
Paste.isRecord(pasteRecord.value) &&
180
-
Paste.validateRecord(pasteRecord.value).success
181
-
? pasteRecord.value
182
-
: {};
226
+
const comments = ret
227
+
.filter((row) => row.commentUri)
228
+
.map((row) => {
229
+
return {
230
+
uri: row.commentUri,
231
+
authorDid: row.commentAuthorDid,
232
+
body: row.commentBody,
233
+
createdAt: row.commentCreatedAt,
234
+
};
235
+
});
183
236
184
-
return res.render("paste", { paste, handle, shortUrl });
237
+
const ownAgent = await getSessionAgent(req, res, ctx);
238
+
if (!ownAgent) {
239
+
return res.render("paste", {
240
+
paste,
241
+
didHandleMap,
242
+
comments,
243
+
});
244
+
} else {
245
+
const ownDid = ownAgent.assertDid;
246
+
didHandleMap[ownDid] = await ctx.resolver.resolveDidToHandle(ownDid);
247
+
return res.render("paste", {
248
+
paste,
249
+
ownDid,
250
+
didHandleMap,
251
+
comments,
252
+
});
253
+
}
185
254
});
186
255
256
+
router.get("/p/:shortUrl/raw", async (req, res) => {
257
+
res.redirect(`/r/${req.params.shortUrl}`);
258
+
});
187
259
router.get("/r/:shortUrl", async (req, res) => {
188
260
const { shortUrl } = req.params;
189
261
const ret = await ctx.db
···
199
271
return res.send(ret.code);
200
272
});
201
273
274
+
router.get("/reset", async (req, res) => {
275
+
const agent = await getSessionAgent(req, res, ctx);
276
+
if (!agent) {
277
+
return res.redirect("/");
278
+
}
279
+
const response = await agent.com.atproto.repo.listRecords({
280
+
repo: agent.assertDid,
281
+
collection: "li.plonk.paste",
282
+
limit: 10,
283
+
});
284
+
const vals = response.data.records;
285
+
for (const v of vals) {
286
+
const aturl = new AtUri(v.uri);
287
+
await agent.com.atproto.repo.deleteRecord({
288
+
repo: agent.assertDid,
289
+
collection: aturl.collection,
290
+
rkey: aturl.rkey,
291
+
});
292
+
}
293
+
return res.redirect("/");
294
+
});
295
+
202
296
router.post("/paste", async (req, res) => {
203
297
const agent = await getSessionAgent(req, res, ctx);
204
298
if (!agent) {
···
209
303
}
210
304
211
305
const rkey = TID.nextStr();
306
+
const shortUrl = await newShortUrl(ctx.db);
212
307
const record = {
213
-
$type: "ovh.plonk.paste",
308
+
$type: "li.plonk.paste",
214
309
code: req.body?.code,
215
310
lang: req.body?.lang,
311
+
shortUrl,
216
312
title: req.body?.title,
217
313
createdAt: new Date().toISOString(),
218
314
};
···
228
324
try {
229
325
const res = await agent.com.atproto.repo.putRecord({
230
326
repo: agent.assertDid,
231
-
collection: "ovh.plonk.paste",
327
+
collection: "li.plonk.paste",
232
328
rkey,
233
329
record,
234
330
validate: false,
···
257
353
indexedAt: new Date().toISOString(),
258
354
})
259
355
.execute();
260
-
ctx.logger.info(res, "wrote back to db");
261
356
return res.redirect(`/p/${shortUrl}`);
262
357
} catch (err) {
263
358
ctx.logger.warn(
···
269
364
return res.redirect("/");
270
365
});
271
366
367
+
router.post("/:paste/comment", async (req, res) => {
368
+
const agent = await getSessionAgent(req, res, ctx);
369
+
370
+
if (!agent) {
371
+
return res
372
+
.status(401)
373
+
.type("html")
374
+
.send("<h1>Error: Session required</h1>");
375
+
}
376
+
377
+
const pasteUri = req.params.paste;
378
+
const aturi = new AtUri(pasteUri);
379
+
const pasteResponse = await agent.com.atproto.repo.getRecord({
380
+
repo: aturi.hostname,
381
+
collection: aturi.collection,
382
+
rkey: aturi.rkey,
383
+
});
384
+
const pasteCid = pasteResponse.data.cid;
385
+
if (!pasteCid) {
386
+
return res.status(401).type("html").send("invalid paste");
387
+
}
388
+
389
+
const rkey = TID.nextStr();
390
+
const record = {
391
+
$type: "li.plonk.comment",
392
+
content: req.body?.comment,
393
+
post: {
394
+
uri: pasteUri,
395
+
cid: pasteCid,
396
+
},
397
+
createdAt: new Date().toISOString(),
398
+
};
399
+
400
+
if (!Comment.validateRecord(record).success) {
401
+
return res
402
+
.status(400)
403
+
.type("html")
404
+
.send("<h1>Error: Invalid status</h1>");
405
+
}
406
+
407
+
let uri;
408
+
try {
409
+
const res = await agent.com.atproto.repo.putRecord({
410
+
repo: agent.assertDid,
411
+
collection: "li.plonk.comment",
412
+
rkey,
413
+
record,
414
+
validate: false,
415
+
});
416
+
uri = res.data.uri;
417
+
} catch (err) {
418
+
ctx.logger.warn({ err }, "failed to put record");
419
+
return res
420
+
.status(500)
421
+
.type("html")
422
+
.send("<h3>Error: Failed to write record</h1>");
423
+
}
424
+
425
+
try {
426
+
await ctx.db
427
+
.insertInto("comment")
428
+
.values({
429
+
uri,
430
+
body: record.content,
431
+
authorDid: agent.assertDid,
432
+
pasteUri: record.post.uri,
433
+
pasteCid: record.post.cid,
434
+
createdAt: record.createdAt,
435
+
indexedAt: new Date().toISOString(),
436
+
})
437
+
.execute();
438
+
const originalPaste = await ctx.db
439
+
.selectFrom("paste")
440
+
.selectAll()
441
+
.where("uri", "=", pasteUri)
442
+
.executeTakeFirst();
443
+
return res.redirect(
444
+
`/p/${originalPaste.shortUrl}#${encodeURIComponent(uri)}`,
445
+
);
446
+
} catch (err) {
447
+
ctx.logger.warn(
448
+
{ err },
449
+
"failed to update computed view; ignoring as it should be caught by the firehose",
450
+
);
451
+
}
452
+
453
+
return res.redirect("/");
454
+
});
455
+
272
456
return router;
273
457
};
274
-
275
-
// https://pds.icyphox.sh/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3A3ft67n4xnawzq4qi7mcksxj5
276
-
// at://did:plc:3ft67n4xnawzq4qi7mcksxj5/ovh.plonk.paste/3lcs3lnslbk2d
277
-
// https://pds.icyphox.sh/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3A3ft67n4xnawzq4qi7mcksxj5&collection=ovh.plonk.paste&rkey=3lcqt7newvc2c
+10
-15
src/views/index.pug
+10
-15
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
···
7
9
-
8
10
var langs = ["plaintext"]
9
11
.concat([
12
+
"bash",
10
13
"javascript",
11
14
"typescript",
12
15
"java",
···
18
21
"c",
19
22
"c#",
20
23
"c++",
24
+
"cobol",
25
+
"nix",
21
26
].toSorted())
22
27
doctype html
23
28
html
24
29
+head("timeline")
25
30
body
26
31
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'
32
+
+header(ownDid, didHandleMap)
41
33
42
34
if ownDid
43
35
+mkPost()
44
36
45
37
div.timeline
46
38
each paste in pastes
47
-
+post(paste, didHandleMap)
39
+
- var handle = didHandleMap[paste.authorDid]
40
+
+post(paste, handle, paste.authorDid)
41
+
42
+
+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()
+62
-6
src/views/paste.pug
+62
-6
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 @#{handle} ยท
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
16
-
pre
17
-
| #{paste.code}
18
+
a(href=`/r/${paste.shortUrl}`) raw
19
+
| ยท
20
+
button#copy-btn(type="button" onclick="copyToClipboard()" data-code=paste.code) copy
21
+
| ยท
22
+
| #{comments.length} #{pluralize(comments.length, 'comment')}
23
+
div.highlighted-code !{paste.highlightedCode}
24
+
hr
25
+
26
+
if comments.length != 0
27
+
h1(id="comments") comments
28
+
div.comments
29
+
each comment in comments
30
+
div.comment(id=`${encodeURIComponent(comment.uri)}`)
31
+
p.comment-info
32
+
a(href=`/u/${comment.authorDid}`)
33
+
| @#{didHandleMap[comment.authorDid]}
34
+
| ยท
35
+
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
36
+
pre.comment-body #{comment.body}
37
+
38
+
if ownDid
39
+
form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
40
+
div.post-row
41
+
textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code
42
+
43
+
div.post-submit-row
44
+
button(type="submit").post-input-submit zonk!
45
+
else
46
+
p
47
+
a(href="/login") login
48
+
| to post a comment
49
+
50
+
+footer()
51
+
script.
52
+
async function copyToClipboard() {
53
+
const copyBtn = document.getElementById('copy-btn');
54
+
const originalText = copyBtn.textContent;
55
+
56
+
try {
57
+
const code = copyBtn.dataset.code;
58
+
await navigator.clipboard.writeText(code);
59
+
copyBtn.textContent = 'copied!';
60
+
copyBtn.style.color = 'var(--accent)';
61
+
62
+
setTimeout(() => {
63
+
copyBtn.textContent = originalText;
64
+
copyBtn.style.color = '';
65
+
}, 1500);
66
+
} catch (err) {
67
+
console.error('Failed to copy: ', err);
68
+
copyBtn.textContent = 'copy failed';
69
+
setTimeout(() => {
70
+
copyBtn.textContent = originalText;
71
+
}, 1500);
72
+
}
73
+
}
+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, didHandleMap)
17
+
+post(paste, handle, authorDid)
18
+
19
+
+footer()