tangled
alpha
login
or
join now
mary.my.id
/
boat
22
fork
atom
handy online tools for AT Protocol
boat.kelinci.net
atproto
bluesky
atcute
typescript
solidjs
22
fork
atom
overview
issues
pulls
pipelines
refactor: some cleaning up
mary.my.id
1 year ago
15ff3a61
8b0b04fd
verified
This commit was signed with the committer's
known signature
.
mary.my.id
SSH Key Fingerprint:
SHA256:ZlTP/auFSGpGnaoDg4mCTG1g9OZvXp62jWR4c6H4O3c=
+119
-78
10 changed files
expand all
collapse all
unified
split
package.json
pnpm-lock.yaml
src
api
queries
plc.ts
types
plc.ts
strings.ts
views
blob
blob-export.tsx
identity
did-lookup.tsx
plc-oplogs.tsx
repository
repo-export.tsx
tailwind.config.js
+1
package.json
···
21
21
"valibot": "1.0.0-beta.2"
22
22
},
23
23
"devDependencies": {
24
24
+
"@tailwindcss/forms": "^0.5.9",
24
25
"@types/node": "^22.8.2",
25
26
"autoprefixer": "^10.4.20",
26
27
"prettier": "^3.3.3",
+19
pnpm-lock.yaml
···
45
45
specifier: 1.0.0-beta.2
46
46
version: 1.0.0-beta.2(typescript@5.7.0-beta)
47
47
devDependencies:
48
48
+
'@tailwindcss/forms':
49
49
+
specifier: ^0.5.9
50
50
+
version: 0.5.9(tailwindcss@3.4.14)
48
51
'@types/node':
49
52
specifier: ^22.8.2
50
53
version: 22.8.2
···
657
660
cpu: [x64]
658
661
os: [win32]
659
662
663
663
+
'@tailwindcss/forms@0.5.9':
664
664
+
resolution: {integrity: sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==}
665
665
+
peerDependencies:
666
666
+
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20'
667
667
+
660
668
'@types/babel__core@7.20.5':
661
669
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
662
670
···
1024
1032
engines: {node: '>=10.0.0'}
1025
1033
hasBin: true
1026
1034
1035
1035
+
mini-svg-data-uri@1.4.4:
1036
1036
+
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
1037
1037
+
hasBin: true
1038
1038
+
1027
1039
miniflare@3.20241022.0:
1028
1040
resolution: {integrity: sha512-x9Fbq1Hmz1f0osIT9Qmj78iX4UpCP2EqlZnA/tzj/3+I49vc3Kq0fNqSSKplcdf6HlCHdL3fOBicmreQF4BUUQ==}
1029
1041
engines: {node: '>=16.13'}
···
1965
1977
'@rollup/rollup-win32-x64-msvc@4.24.2':
1966
1978
optional: true
1967
1979
1980
1980
+
'@tailwindcss/forms@0.5.9(tailwindcss@3.4.14)':
1981
1981
+
dependencies:
1982
1982
+
mini-svg-data-uri: 1.4.4
1983
1983
+
tailwindcss: 3.4.14
1984
1984
+
1968
1985
'@types/babel__core@7.20.5':
1969
1986
dependencies:
1970
1987
'@babel/parser': 7.26.1
···
2337
2354
picomatch: 2.3.1
2338
2355
2339
2356
mime@3.0.0: {}
2357
2357
+
2358
2358
+
mini-svg-data-uri@1.4.4: {}
2340
2359
2341
2360
miniflare@3.20241022.0:
2342
2361
dependencies:
+15
src/api/queries/plc.ts
···
1
1
+
import * as v from 'valibot';
2
2
+
3
3
+
import { At } from '@atcute/client/lexicons';
4
4
+
5
5
+
import { plcLogEntries } from '../types/plc';
6
6
+
7
7
+
export const getPlcAuditLogs = async ({ did, signal }: { did: At.DID; signal?: AbortSignal }) => {
8
8
+
const response = await fetch(`https://plc.directory/${did}/log/audit`, { signal });
9
9
+
if (!response.ok) {
10
10
+
throw new Error(`got resposne ${response.status}`);
11
11
+
}
12
12
+
13
13
+
const json = await response.json();
14
14
+
return v.parse(plcLogEntries, json);
15
15
+
};
+64
src/api/types/plc.ts
···
1
1
+
import * as v from 'valibot';
2
2
+
3
3
+
import { didKeyString, didString, handleString, serviceUrlString } from './strings';
4
4
+
5
5
+
export const legacyGenesisOp = v.object({
6
6
+
type: v.literal('create'),
7
7
+
signingKey: didKeyString,
8
8
+
recoveryKey: didKeyString,
9
9
+
handle: handleString,
10
10
+
service: serviceUrlString,
11
11
+
prev: v.null(),
12
12
+
sig: v.string(),
13
13
+
});
14
14
+
export type PlcLegacyGenesisOp = v.InferOutput<typeof legacyGenesisOp>;
15
15
+
16
16
+
export const tombstoneOp = v.object({
17
17
+
type: v.literal('plc_tombstone'),
18
18
+
prev: v.string(),
19
19
+
sig: v.string(),
20
20
+
});
21
21
+
export type PlcTombstoneOp = v.InferOutput<typeof tombstoneOp>;
22
22
+
23
23
+
export const service = v.object({
24
24
+
type: v.string(),
25
25
+
endpoint: v.pipe(v.string(), v.url()),
26
26
+
});
27
27
+
export type Service = v.InferOutput<typeof service>;
28
28
+
29
29
+
const updateOp = v.object({
30
30
+
type: v.literal('plc_operation'),
31
31
+
rotationKeys: v.array(didKeyString),
32
32
+
verificationMethods: v.record(v.string(), didKeyString),
33
33
+
alsoKnownAs: v.array(v.pipe(v.string(), v.url())),
34
34
+
services: v.record(
35
35
+
v.string(),
36
36
+
v.object({
37
37
+
type: v.string(),
38
38
+
endpoint: v.pipe(v.string(), v.url()),
39
39
+
}),
40
40
+
),
41
41
+
prev: v.nullable(v.string()),
42
42
+
sig: v.string(),
43
43
+
});
44
44
+
export type PlcUpdateOp = v.InferOutput<typeof updateOp>;
45
45
+
46
46
+
export const plcOperation = v.union([legacyGenesisOp, tombstoneOp, updateOp]);
47
47
+
export type PlcOperation = v.InferOutput<typeof plcOperation>;
48
48
+
49
49
+
export const plcLogEntry = v.object({
50
50
+
did: didString,
51
51
+
cid: v.string(),
52
52
+
operation: plcOperation,
53
53
+
nullified: v.boolean(),
54
54
+
createdAt: v.pipe(
55
55
+
v.string(),
56
56
+
v.check((dateString) => {
57
57
+
const date = new Date(dateString);
58
58
+
return !Number.isNaN(date.getTime());
59
59
+
}),
60
60
+
),
61
61
+
});
62
62
+
export type PlcLogEntry = v.InferOutput<typeof plcLogEntry>;
63
63
+
64
64
+
export const plcLogEntries = v.array(plcLogEntry);
+5
src/api/types/strings.ts
···
19
19
);
20
20
}, 'must be a valid service url'),
21
21
);
22
22
+
23
23
+
export const didKeyString = v.pipe(
24
24
+
v.string(),
25
25
+
v.check((str) => str.startsWith('did:key:')),
26
26
+
);
+2
-2
src/views/blob/blob-export.tsx
···
260
260
required
261
261
pattern={DID_OR_HANDLE_RE.source}
262
262
placeholder="paul.bsky.social"
263
263
-
class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline"
263
263
+
class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0"
264
264
/>
265
265
</label>
266
266
···
280
280
input.setCustomValidity('');
281
281
}
282
282
}}
283
283
-
class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline"
283
283
+
class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0"
284
284
/>
285
285
</label>
286
286
+1
-1
src/views/identity/did-lookup.tsx
···
73
73
pattern={DID_OR_HANDLE_RE.source}
74
74
placeholder="paul.bsky.social"
75
75
value={params.q ?? ''}
76
76
-
class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline"
76
76
+
class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0"
77
77
/>
78
78
</label>
79
79
+7
-73
src/views/identity/plc-oplogs.tsx
···
1
1
import { createSignal, JSX, Match, onCleanup, Switch } from 'solid-js';
2
2
-
import * as v from 'valibot';
3
2
4
3
import { At } from '@atcute/client/lexicons';
5
4
6
5
import { resolveHandleViaAppView } from '~/api/queries/handle';
7
7
-
import { didString, handleString, serviceUrlString } from '~/api/types/strings';
6
6
+
import { PlcLogEntry, Service } from '~/api/types/plc';
8
7
import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings';
9
8
9
9
+
import { getPlcAuditLogs } from '~/api/queries/plc';
10
10
import { useTitle } from '~/lib/navigation/router';
11
11
import { dequal } from '~/lib/utils/dequal';
12
12
import { createQuery } from '~/lib/utils/query';
···
15
15
import CircularProgressView from '~/components/circular-progress-view';
16
16
import DiffTable from '~/components/diff-table';
17
17
import ErrorView from '~/components/error-view';
18
18
+
19
19
+
import CheckIcon from '~/components/ic-icons/baseline-check';
18
20
import ContentCopyIcon from '~/components/ic-icons/baseline-content-copy';
19
19
-
import CheckIcon from '~/components/ic-icons/baseline-check';
20
21
21
22
const PlcOperationLogPage = () => {
22
23
const [params, setParams] = useSearchParams({
···
37
38
throw new Error(`${did} is not plc`);
38
39
}
39
40
40
40
-
const response = await fetch(`https://plc.directory/${did}/log/audit`);
41
41
-
if (!response.ok) {
42
42
-
throw new Error(`got resposne ${response.status}`);
43
43
-
}
44
44
-
45
45
-
const json = await response.json();
46
46
-
return v.parse(plcLogEntries, json);
41
41
+
const logs = await getPlcAuditLogs({ did, signal });
42
42
+
return logs;
47
43
},
48
44
);
49
45
···
84
80
pattern={DID_OR_HANDLE_RE.source}
85
81
placeholder="paul.bsky.social"
86
82
value={params.q ?? ''}
87
87
-
class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline"
83
83
+
class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0"
88
84
/>
89
85
</label>
90
86
···
711
707
712
708
return history;
713
709
};
714
714
-
715
715
-
const didKeyString = v.pipe(
716
716
-
v.string(),
717
717
-
v.check((str) => str.startsWith('did:key:')),
718
718
-
);
719
719
-
720
720
-
const legacyGenesisOp = v.object({
721
721
-
type: v.literal('create'),
722
722
-
signingKey: didKeyString,
723
723
-
recoveryKey: didKeyString,
724
724
-
handle: handleString,
725
725
-
service: serviceUrlString,
726
726
-
prev: v.null(),
727
727
-
sig: v.string(),
728
728
-
});
729
729
-
730
730
-
const tombstoneOp = v.object({
731
731
-
type: v.literal('plc_tombstone'),
732
732
-
prev: v.string(),
733
733
-
sig: v.string(),
734
734
-
});
735
735
-
736
736
-
const service = v.object({
737
737
-
type: v.string(),
738
738
-
endpoint: v.pipe(v.string(), v.url()),
739
739
-
});
740
740
-
type Service = v.InferOutput<typeof service>;
741
741
-
742
742
-
const plcOp = v.object({
743
743
-
type: v.literal('plc_operation'),
744
744
-
rotationKeys: v.array(didKeyString),
745
745
-
verificationMethods: v.record(v.string(), didKeyString),
746
746
-
alsoKnownAs: v.array(v.pipe(v.string(), v.url())),
747
747
-
services: v.record(
748
748
-
v.string(),
749
749
-
v.object({
750
750
-
type: v.string(),
751
751
-
endpoint: v.pipe(v.string(), v.url()),
752
752
-
}),
753
753
-
),
754
754
-
prev: v.nullable(v.string()),
755
755
-
sig: v.string(),
756
756
-
});
757
757
-
758
758
-
const plcOperation = v.union([legacyGenesisOp, tombstoneOp, plcOp]);
759
759
-
760
760
-
const plcLogEntry = v.object({
761
761
-
did: didString,
762
762
-
cid: v.string(),
763
763
-
operation: plcOperation,
764
764
-
nullified: v.boolean(),
765
765
-
createdAt: v.pipe(
766
766
-
v.string(),
767
767
-
v.check((dateString) => {
768
768
-
const date = new Date(dateString);
769
769
-
return !Number.isNaN(date.getTime());
770
770
-
}),
771
771
-
),
772
772
-
});
773
773
-
type PlcLogEntry = v.InferOutput<typeof plcLogEntry>;
774
774
-
775
775
-
const plcLogEntries = v.array(plcLogEntry);
776
710
777
711
function findLastMatching<T, S extends T>(
778
712
arr: T[],
+2
-2
src/views/repository/repo-export.tsx
···
186
186
required
187
187
pattern={DID_OR_HANDLE_RE.source}
188
188
placeholder="paul.bsky.social"
189
189
-
class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline"
189
189
+
class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0"
190
190
/>
191
191
</label>
192
192
···
206
206
input.setCustomValidity('');
207
207
}
208
208
}}
209
209
-
class="rounded border border-gray-400 px-3 py-2 text-sm outline-2 -outline-offset-1 outline-purple-600 placeholder:text-gray-400 focus:outline"
209
209
+
class="rounded border border-gray-400 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0"
210
210
/>
211
211
</label>
212
212
+3
tailwind.config.js
···
1
1
import plugin from 'tailwindcss/plugin';
2
2
3
3
+
import forms from '@tailwindcss/forms';
4
4
+
3
5
/** @type {import('tailwindcss').Config} */
4
6
export default {
5
7
content: ['./src/**/*.tsx'],
···
16
18
hoverOnlyWhenSupported: true,
17
19
},
18
20
plugins: [
21
21
+
forms(),
19
22
plugin(({ addVariant, addUtilities }) => {
20
23
addVariant('modal', '&:modal');
21
24
addVariant('focus-within', '&:has(:focus-visible)');