tangled
alpha
login
or
join now
grain.social
/
grain
38
fork
atom
grain.social is a photo sharing platform built on atproto.
38
fork
atom
overview
issues
2
pulls
pipelines
feat: start working on xrpc routes
chadtmiller.com
8 months ago
6005b6fd
2e71f511
+1173
-9
22 changed files
expand all
collapse all
unified
split
__generated__
index.ts
lexicons.ts
types
social
grain
actor
defs.ts
getProfile.ts
feed
getTimeline.ts
gallery
getActorGalleries.ts
getGallery.ts
getGalleryThread.ts
deno.json
deno.lock
lexicons
social
grain
actor
defs.json
getProfile.json
feed
getTimeline.json
gallery
getActorGalleries.json
getGallery.json
getGalleryThread.json
src
api
mod.ts
lib
actor.ts
follow.ts
gallery.ts
main.tsx
modules
comments.tsx
+70
__generated__/index.ts
···
9
9
type StreamAuthVerifier,
10
10
} from "npm:@atproto/xrpc-server"
11
11
import { schemas } from './lexicons.ts'
12
12
+
import * as SocialGrainGalleryGetGalleryThread from './types/social/grain/gallery/getGalleryThread.ts'
13
13
+
import * as SocialGrainGalleryGetActorGalleries from './types/social/grain/gallery/getActorGalleries.ts'
14
14
+
import * as SocialGrainGalleryGetGallery from './types/social/grain/gallery/getGallery.ts'
15
15
+
import * as SocialGrainFeedGetTimeline from './types/social/grain/feed/getTimeline.ts'
16
16
+
import * as SocialGrainActorGetProfile from './types/social/grain/actor/getProfile.ts'
12
17
13
18
export const APP_BSKY_GRAPH = {
14
19
DefsModlist: 'app.bsky.graph.defs#modlist',
···
182
187
gallery: SocialGrainGalleryNS
183
188
graph: SocialGrainGraphNS
184
189
labeler: SocialGrainLabelerNS
190
190
+
feed: SocialGrainFeedNS
185
191
actor: SocialGrainActorNS
186
192
photo: SocialGrainPhotoNS
187
193
···
190
196
this.gallery = new SocialGrainGalleryNS(server)
191
197
this.graph = new SocialGrainGraphNS(server)
192
198
this.labeler = new SocialGrainLabelerNS(server)
199
199
+
this.feed = new SocialGrainFeedNS(server)
193
200
this.actor = new SocialGrainActorNS(server)
194
201
this.photo = new SocialGrainPhotoNS(server)
195
202
}
···
201
208
constructor(server: Server) {
202
209
this._server = server
203
210
}
211
211
+
212
212
+
getGalleryThread<AV extends AuthVerifier>(
213
213
+
cfg: ConfigOf<
214
214
+
AV,
215
215
+
SocialGrainGalleryGetGalleryThread.Handler<ExtractAuth<AV>>,
216
216
+
SocialGrainGalleryGetGalleryThread.HandlerReqCtx<ExtractAuth<AV>>
217
217
+
>,
218
218
+
) {
219
219
+
const nsid = 'social.grain.gallery.getGalleryThread' // @ts-ignore
220
220
+
return this._server.xrpc.method(nsid, cfg)
221
221
+
}
222
222
+
223
223
+
getActorGalleries<AV extends AuthVerifier>(
224
224
+
cfg: ConfigOf<
225
225
+
AV,
226
226
+
SocialGrainGalleryGetActorGalleries.Handler<ExtractAuth<AV>>,
227
227
+
SocialGrainGalleryGetActorGalleries.HandlerReqCtx<ExtractAuth<AV>>
228
228
+
>,
229
229
+
) {
230
230
+
const nsid = 'social.grain.gallery.getActorGalleries' // @ts-ignore
231
231
+
return this._server.xrpc.method(nsid, cfg)
232
232
+
}
233
233
+
234
234
+
getGallery<AV extends AuthVerifier>(
235
235
+
cfg: ConfigOf<
236
236
+
AV,
237
237
+
SocialGrainGalleryGetGallery.Handler<ExtractAuth<AV>>,
238
238
+
SocialGrainGalleryGetGallery.HandlerReqCtx<ExtractAuth<AV>>
239
239
+
>,
240
240
+
) {
241
241
+
const nsid = 'social.grain.gallery.getGallery' // @ts-ignore
242
242
+
return this._server.xrpc.method(nsid, cfg)
243
243
+
}
204
244
}
205
245
206
246
export class SocialGrainGraphNS {
···
219
259
}
220
260
}
221
261
262
262
+
export class SocialGrainFeedNS {
263
263
+
_server: Server
264
264
+
265
265
+
constructor(server: Server) {
266
266
+
this._server = server
267
267
+
}
268
268
+
269
269
+
getTimeline<AV extends AuthVerifier>(
270
270
+
cfg: ConfigOf<
271
271
+
AV,
272
272
+
SocialGrainFeedGetTimeline.Handler<ExtractAuth<AV>>,
273
273
+
SocialGrainFeedGetTimeline.HandlerReqCtx<ExtractAuth<AV>>
274
274
+
>,
275
275
+
) {
276
276
+
const nsid = 'social.grain.feed.getTimeline' // @ts-ignore
277
277
+
return this._server.xrpc.method(nsid, cfg)
278
278
+
}
279
279
+
}
280
280
+
222
281
export class SocialGrainActorNS {
223
282
_server: Server
224
283
225
284
constructor(server: Server) {
226
285
this._server = server
286
286
+
}
287
287
+
288
288
+
getProfile<AV extends AuthVerifier>(
289
289
+
cfg: ConfigOf<
290
290
+
AV,
291
291
+
SocialGrainActorGetProfile.Handler<ExtractAuth<AV>>,
292
292
+
SocialGrainActorGetProfile.HandlerReqCtx<ExtractAuth<AV>>
293
293
+
>,
294
294
+
) {
295
295
+
const nsid = 'social.grain.actor.getProfile' // @ts-ignore
296
296
+
return this._server.xrpc.method(nsid, cfg)
227
297
}
228
298
}
229
299
+283
__generated__/lexicons.ts
···
2718
2718
},
2719
2719
},
2720
2720
},
2721
2721
+
SocialGrainGalleryGetGalleryThread: {
2722
2722
+
lexicon: 1,
2723
2723
+
id: 'social.grain.gallery.getGalleryThread',
2724
2724
+
defs: {
2725
2725
+
main: {
2726
2726
+
type: 'query',
2727
2727
+
description:
2728
2728
+
'Gets a hydrated gallery view and its comments for a specified gallery AT-URI.',
2729
2729
+
parameters: {
2730
2730
+
type: 'params',
2731
2731
+
required: ['uri'],
2732
2732
+
properties: {
2733
2733
+
uri: {
2734
2734
+
type: 'string',
2735
2735
+
description:
2736
2736
+
'The AT-URI of the gallery to return a hydrated view and comments for.',
2737
2737
+
format: 'at-uri',
2738
2738
+
},
2739
2739
+
},
2740
2740
+
},
2741
2741
+
output: {
2742
2742
+
encoding: 'application/json',
2743
2743
+
schema: {
2744
2744
+
type: 'object',
2745
2745
+
required: ['gallery', 'comments'],
2746
2746
+
properties: {
2747
2747
+
gallery: {
2748
2748
+
type: 'ref',
2749
2749
+
ref: 'lex:social.grain.gallery.defs#galleryView',
2750
2750
+
},
2751
2751
+
comments: {
2752
2752
+
type: 'array',
2753
2753
+
items: {
2754
2754
+
type: 'ref',
2755
2755
+
ref: 'lex:social.grain.comment.defs#commentView',
2756
2756
+
},
2757
2757
+
},
2758
2758
+
},
2759
2759
+
},
2760
2760
+
},
2761
2761
+
},
2762
2762
+
},
2763
2763
+
},
2764
2764
+
SocialGrainGalleryGetActorGalleries: {
2765
2765
+
lexicon: 1,
2766
2766
+
id: 'social.grain.gallery.getActorGalleries',
2767
2767
+
defs: {
2768
2768
+
main: {
2769
2769
+
type: 'query',
2770
2770
+
description:
2771
2771
+
"Get a view of an actor's galleries. Does not require auth.",
2772
2772
+
parameters: {
2773
2773
+
type: 'params',
2774
2774
+
required: ['actor'],
2775
2775
+
properties: {
2776
2776
+
actor: {
2777
2777
+
type: 'string',
2778
2778
+
format: 'at-identifier',
2779
2779
+
},
2780
2780
+
limit: {
2781
2781
+
type: 'integer',
2782
2782
+
minimum: 1,
2783
2783
+
maximum: 100,
2784
2784
+
default: 50,
2785
2785
+
},
2786
2786
+
cursor: {
2787
2787
+
type: 'string',
2788
2788
+
},
2789
2789
+
},
2790
2790
+
},
2791
2791
+
output: {
2792
2792
+
encoding: 'application/json',
2793
2793
+
schema: {
2794
2794
+
type: 'object',
2795
2795
+
required: ['items'],
2796
2796
+
properties: {
2797
2797
+
cursor: {
2798
2798
+
type: 'string',
2799
2799
+
},
2800
2800
+
items: {
2801
2801
+
type: 'array',
2802
2802
+
items: {
2803
2803
+
type: 'ref',
2804
2804
+
ref: 'lex:social.grain.gallery.defs#galleryView',
2805
2805
+
},
2806
2806
+
},
2807
2807
+
},
2808
2808
+
},
2809
2809
+
},
2810
2810
+
errors: [
2811
2811
+
{
2812
2812
+
name: 'BlockedActor',
2813
2813
+
},
2814
2814
+
{
2815
2815
+
name: 'BlockedByActor',
2816
2816
+
},
2817
2817
+
],
2818
2818
+
},
2819
2819
+
},
2820
2820
+
},
2821
2821
+
SocialGrainGalleryGetGallery: {
2822
2822
+
lexicon: 1,
2823
2823
+
id: 'social.grain.gallery.getGallery',
2824
2824
+
defs: {
2825
2825
+
main: {
2826
2826
+
type: 'query',
2827
2827
+
description:
2828
2828
+
'Gets a hydrated gallery view for a specified gallery AT-URI.',
2829
2829
+
parameters: {
2830
2830
+
type: 'params',
2831
2831
+
required: ['uri'],
2832
2832
+
properties: {
2833
2833
+
uri: {
2834
2834
+
type: 'string',
2835
2835
+
description:
2836
2836
+
'The AT-URI of the gallery to return a hydrated view for.',
2837
2837
+
format: 'at-uri',
2838
2838
+
},
2839
2839
+
},
2840
2840
+
},
2841
2841
+
output: {
2842
2842
+
encoding: 'application/json',
2843
2843
+
schema: {
2844
2844
+
type: 'ref',
2845
2845
+
ref: 'lex:social.grain.gallery.defs#galleryView',
2846
2846
+
},
2847
2847
+
},
2848
2848
+
},
2849
2849
+
},
2850
2850
+
},
2721
2851
SocialGrainGraphFollow: {
2722
2852
lexicon: 1,
2723
2853
id: 'social.grain.graph.follow',
···
2942
3072
},
2943
3073
},
2944
3074
},
3075
3075
+
SocialGrainFeedGetTimeline: {
3076
3076
+
lexicon: 1,
3077
3077
+
id: 'social.grain.feed.getTimeline',
3078
3078
+
defs: {
3079
3079
+
main: {
3080
3080
+
type: 'query',
3081
3081
+
description: "Get a view of the requesting account's home timeline.",
3082
3082
+
parameters: {
3083
3083
+
type: 'params',
3084
3084
+
properties: {
3085
3085
+
algorithm: {
3086
3086
+
type: 'string',
3087
3087
+
description:
3088
3088
+
"Variant 'algorithm' for timeline. Implementation-specific.",
3089
3089
+
},
3090
3090
+
limit: {
3091
3091
+
type: 'integer',
3092
3092
+
minimum: 1,
3093
3093
+
maximum: 100,
3094
3094
+
default: 50,
3095
3095
+
},
3096
3096
+
cursor: {
3097
3097
+
type: 'string',
3098
3098
+
},
3099
3099
+
},
3100
3100
+
},
3101
3101
+
output: {
3102
3102
+
encoding: 'application/json',
3103
3103
+
schema: {
3104
3104
+
type: 'object',
3105
3105
+
required: ['feed'],
3106
3106
+
properties: {
3107
3107
+
cursor: {
3108
3108
+
type: 'string',
3109
3109
+
},
3110
3110
+
feed: {
3111
3111
+
type: 'array',
3112
3112
+
items: {
3113
3113
+
type: 'ref',
3114
3114
+
ref: 'lex:social.grain.gallery.defs#galleryView',
3115
3115
+
},
3116
3116
+
},
3117
3117
+
},
3118
3118
+
},
3119
3119
+
},
3120
3120
+
},
3121
3121
+
},
3122
3122
+
},
2945
3123
SocialGrainFavorite: {
2946
3124
lexicon: 1,
2947
3125
id: 'social.grain.favorite',
···
3006
3184
createdAt: {
3007
3185
type: 'string',
3008
3186
format: 'datetime',
3187
3187
+
},
3188
3188
+
},
3189
3189
+
},
3190
3190
+
profileViewDetailed: {
3191
3191
+
type: 'object',
3192
3192
+
required: ['did', 'handle'],
3193
3193
+
properties: {
3194
3194
+
did: {
3195
3195
+
type: 'string',
3196
3196
+
format: 'did',
3197
3197
+
},
3198
3198
+
handle: {
3199
3199
+
type: 'string',
3200
3200
+
format: 'handle',
3201
3201
+
},
3202
3202
+
displayName: {
3203
3203
+
type: 'string',
3204
3204
+
maxGraphemes: 64,
3205
3205
+
maxLength: 640,
3206
3206
+
},
3207
3207
+
description: {
3208
3208
+
type: 'string',
3209
3209
+
maxGraphemes: 256,
3210
3210
+
maxLength: 2560,
3211
3211
+
},
3212
3212
+
avatar: {
3213
3213
+
type: 'string',
3214
3214
+
format: 'uri',
3215
3215
+
},
3216
3216
+
followersCount: {
3217
3217
+
type: 'integer',
3218
3218
+
},
3219
3219
+
followsCount: {
3220
3220
+
type: 'integer',
3221
3221
+
},
3222
3222
+
galleryCount: {
3223
3223
+
type: 'integer',
3224
3224
+
},
3225
3225
+
indexedAt: {
3226
3226
+
type: 'string',
3227
3227
+
format: 'datetime',
3228
3228
+
},
3229
3229
+
createdAt: {
3230
3230
+
type: 'string',
3231
3231
+
format: 'datetime',
3232
3232
+
},
3233
3233
+
viewer: {
3234
3234
+
type: 'ref',
3235
3235
+
ref: 'lex:social.grain.actor.defs#viewerState',
3236
3236
+
},
3237
3237
+
labels: {
3238
3238
+
type: 'array',
3239
3239
+
items: {
3240
3240
+
type: 'ref',
3241
3241
+
ref: 'lex:com.atproto.label.defs#label',
3242
3242
+
},
3243
3243
+
},
3244
3244
+
},
3245
3245
+
},
3246
3246
+
viewerState: {
3247
3247
+
type: 'object',
3248
3248
+
description:
3249
3249
+
"Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.",
3250
3250
+
properties: {
3251
3251
+
following: {
3252
3252
+
type: 'string',
3253
3253
+
format: 'at-uri',
3254
3254
+
},
3255
3255
+
followedBy: {
3256
3256
+
type: 'string',
3257
3257
+
format: 'at-uri',
3258
3258
+
},
3259
3259
+
},
3260
3260
+
},
3261
3261
+
},
3262
3262
+
},
3263
3263
+
SocialGrainActorGetProfile: {
3264
3264
+
lexicon: 1,
3265
3265
+
id: 'social.grain.actor.getProfile',
3266
3266
+
defs: {
3267
3267
+
main: {
3268
3268
+
type: 'query',
3269
3269
+
description:
3270
3270
+
'Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.',
3271
3271
+
parameters: {
3272
3272
+
type: 'params',
3273
3273
+
required: ['actor'],
3274
3274
+
properties: {
3275
3275
+
actor: {
3276
3276
+
type: 'string',
3277
3277
+
format: 'at-identifier',
3278
3278
+
description: 'Handle or DID of account to fetch profile of.',
3279
3279
+
},
3280
3280
+
},
3281
3281
+
},
3282
3282
+
output: {
3283
3283
+
encoding: 'application/json',
3284
3284
+
schema: {
3285
3285
+
type: 'ref',
3286
3286
+
ref: 'lex:social.grain.actor.defs#profileViewDetailed',
3009
3287
},
3010
3288
},
3011
3289
},
···
3547
3825
SocialGrainGalleryItem: 'social.grain.gallery.item',
3548
3826
SocialGrainGalleryDefs: 'social.grain.gallery.defs',
3549
3827
SocialGrainGallery: 'social.grain.gallery',
3828
3828
+
SocialGrainGalleryGetGalleryThread: 'social.grain.gallery.getGalleryThread',
3829
3829
+
SocialGrainGalleryGetActorGalleries: 'social.grain.gallery.getActorGalleries',
3830
3830
+
SocialGrainGalleryGetGallery: 'social.grain.gallery.getGallery',
3550
3831
SocialGrainGraphFollow: 'social.grain.graph.follow',
3551
3832
SocialGrainLabelerDefs: 'social.grain.labeler.defs',
3552
3833
SocialGrainLabelerService: 'social.grain.labeler.service',
3834
3834
+
SocialGrainFeedGetTimeline: 'social.grain.feed.getTimeline',
3553
3835
SocialGrainFavorite: 'social.grain.favorite',
3554
3836
SocialGrainActorDefs: 'social.grain.actor.defs',
3837
3837
+
SocialGrainActorGetProfile: 'social.grain.actor.getProfile',
3555
3838
SocialGrainActorProfile: 'social.grain.actor.profile',
3556
3839
SocialGrainPhotoDefs: 'social.grain.photo.defs',
3557
3840
SocialGrainPhotoExif: 'social.grain.photo.exif',
+43
__generated__/types/social/grain/actor/defs.ts
···
35
35
export function validateProfileView<V>(v: V) {
36
36
return validate<ProfileView & V>(v, id, hashProfileView)
37
37
}
38
38
+
39
39
+
export interface ProfileViewDetailed {
40
40
+
$type?: 'social.grain.actor.defs#profileViewDetailed'
41
41
+
did: string
42
42
+
handle: string
43
43
+
displayName?: string
44
44
+
description?: string
45
45
+
avatar?: string
46
46
+
followersCount?: number
47
47
+
followsCount?: number
48
48
+
galleryCount?: number
49
49
+
indexedAt?: string
50
50
+
createdAt?: string
51
51
+
viewer?: ViewerState
52
52
+
labels?: ComAtprotoLabelDefs.Label[]
53
53
+
}
54
54
+
55
55
+
const hashProfileViewDetailed = 'profileViewDetailed'
56
56
+
57
57
+
export function isProfileViewDetailed<V>(v: V) {
58
58
+
return is$typed(v, id, hashProfileViewDetailed)
59
59
+
}
60
60
+
61
61
+
export function validateProfileViewDetailed<V>(v: V) {
62
62
+
return validate<ProfileViewDetailed & V>(v, id, hashProfileViewDetailed)
63
63
+
}
64
64
+
65
65
+
/** Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests. */
66
66
+
export interface ViewerState {
67
67
+
$type?: 'social.grain.actor.defs#viewerState'
68
68
+
following?: string
69
69
+
followedBy?: string
70
70
+
}
71
71
+
72
72
+
const hashViewerState = 'viewerState'
73
73
+
74
74
+
export function isViewerState<V>(v: V) {
75
75
+
return is$typed(v, id, hashViewerState)
76
76
+
}
77
77
+
78
78
+
export function validateViewerState<V>(v: V) {
79
79
+
return validate<ViewerState & V>(v, id, hashViewerState)
80
80
+
}
+45
__generated__/types/social/grain/actor/getProfile.ts
···
1
1
+
/**
2
2
+
* GENERATED CODE - DO NOT MODIFY
3
3
+
*/
4
4
+
import { HandlerAuth, HandlerPipeThrough } from "npm:@atproto/xrpc-server";
5
5
+
import express from "npm:express";
6
6
+
import { validate as _validate } from "../../../../lexicons.ts";
7
7
+
import { is$typed as _is$typed } from "../../../../util.ts";
8
8
+
import type * as SocialGrainActorDefs from "./defs.ts";
9
9
+
10
10
+
const is$typed = _is$typed,
11
11
+
validate = _validate;
12
12
+
const id = "social.grain.actor.getProfile";
13
13
+
14
14
+
export interface QueryParams {
15
15
+
/** Handle or DID of account to fetch profile of. */
16
16
+
actor: string;
17
17
+
}
18
18
+
19
19
+
export type InputSchema = undefined;
20
20
+
export type OutputSchema = SocialGrainActorDefs.ProfileViewDetailed;
21
21
+
export type HandlerInput = undefined;
22
22
+
23
23
+
export interface HandlerSuccess {
24
24
+
encoding: "application/json";
25
25
+
body: OutputSchema;
26
26
+
headers?: { [key: string]: string };
27
27
+
}
28
28
+
29
29
+
export interface HandlerError {
30
30
+
status: number;
31
31
+
message?: string;
32
32
+
}
33
33
+
34
34
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
35
35
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
36
36
+
auth: HA;
37
37
+
params: QueryParams;
38
38
+
input: HandlerInput;
39
39
+
req: express.Request;
40
40
+
res: express.Response;
41
41
+
resetRouteRateLimits: () => Promise<void>;
42
42
+
};
43
43
+
export type Handler<HA extends HandlerAuth = never> = (
44
44
+
ctx: HandlerReqCtx<HA>,
45
45
+
) => Promise<HandlerOutput> | HandlerOutput;
+52
__generated__/types/social/grain/feed/getTimeline.ts
···
1
1
+
/**
2
2
+
* GENERATED CODE - DO NOT MODIFY
3
3
+
*/
4
4
+
import { HandlerAuth, HandlerPipeThrough } from "npm:@atproto/xrpc-server";
5
5
+
import express from "npm:express";
6
6
+
import { validate as _validate } from "../../../../lexicons.ts";
7
7
+
import { is$typed as _is$typed } from "../../../../util.ts";
8
8
+
import type * as SocialGrainGalleryDefs from "../gallery/defs.ts";
9
9
+
10
10
+
const is$typed = _is$typed,
11
11
+
validate = _validate;
12
12
+
const id = "social.grain.feed.getTimeline";
13
13
+
14
14
+
export interface QueryParams {
15
15
+
/** Variant 'algorithm' for timeline. Implementation-specific. */
16
16
+
algorithm?: string;
17
17
+
limit: number;
18
18
+
cursor?: string;
19
19
+
}
20
20
+
21
21
+
export type InputSchema = undefined;
22
22
+
23
23
+
export interface OutputSchema {
24
24
+
cursor?: string;
25
25
+
feed: SocialGrainGalleryDefs.GalleryView[];
26
26
+
}
27
27
+
28
28
+
export type HandlerInput = undefined;
29
29
+
30
30
+
export interface HandlerSuccess {
31
31
+
encoding: "application/json";
32
32
+
body: OutputSchema;
33
33
+
headers?: { [key: string]: string };
34
34
+
}
35
35
+
36
36
+
export interface HandlerError {
37
37
+
status: number;
38
38
+
message?: string;
39
39
+
}
40
40
+
41
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
42
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
43
+
auth: HA;
44
44
+
params: QueryParams;
45
45
+
input: HandlerInput;
46
46
+
req: express.Request;
47
47
+
res: express.Response;
48
48
+
resetRouteRateLimits: () => Promise<void>;
49
49
+
};
50
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
51
+
ctx: HandlerReqCtx<HA>,
52
52
+
) => Promise<HandlerOutput> | HandlerOutput;
+52
__generated__/types/social/grain/gallery/getActorGalleries.ts
···
1
1
+
/**
2
2
+
* GENERATED CODE - DO NOT MODIFY
3
3
+
*/
4
4
+
import { HandlerAuth, HandlerPipeThrough } from "npm:@atproto/xrpc-server";
5
5
+
import express from "npm:express";
6
6
+
import { validate as _validate } from "../../../../lexicons.ts";
7
7
+
import { is$typed as _is$typed } from "../../../../util.ts";
8
8
+
import type * as SocialGrainGalleryDefs from "./defs.ts";
9
9
+
10
10
+
const is$typed = _is$typed,
11
11
+
validate = _validate;
12
12
+
const id = "social.grain.gallery.getActorGalleries";
13
13
+
14
14
+
export interface QueryParams {
15
15
+
actor: string;
16
16
+
limit: number;
17
17
+
cursor?: string;
18
18
+
}
19
19
+
20
20
+
export type InputSchema = undefined;
21
21
+
22
22
+
export interface OutputSchema {
23
23
+
cursor?: string;
24
24
+
items: SocialGrainGalleryDefs.GalleryView[];
25
25
+
}
26
26
+
27
27
+
export type HandlerInput = undefined;
28
28
+
29
29
+
export interface HandlerSuccess {
30
30
+
encoding: "application/json";
31
31
+
body: OutputSchema;
32
32
+
headers?: { [key: string]: string };
33
33
+
}
34
34
+
35
35
+
export interface HandlerError {
36
36
+
status: number;
37
37
+
message?: string;
38
38
+
error?: "BlockedActor" | "BlockedByActor";
39
39
+
}
40
40
+
41
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
42
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
43
+
auth: HA;
44
44
+
params: QueryParams;
45
45
+
input: HandlerInput;
46
46
+
req: express.Request;
47
47
+
res: express.Response;
48
48
+
resetRouteRateLimits: () => Promise<void>;
49
49
+
};
50
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
51
+
ctx: HandlerReqCtx<HA>,
52
52
+
) => Promise<HandlerOutput> | HandlerOutput;
+45
__generated__/types/social/grain/gallery/getGallery.ts
···
1
1
+
/**
2
2
+
* GENERATED CODE - DO NOT MODIFY
3
3
+
*/
4
4
+
import { HandlerAuth, HandlerPipeThrough } from "npm:@atproto/xrpc-server";
5
5
+
import express from "npm:express";
6
6
+
import { validate as _validate } from "../../../../lexicons.ts";
7
7
+
import { is$typed as _is$typed } from "../../../../util.ts";
8
8
+
import type * as SocialGrainGalleryDefs from "./defs.ts";
9
9
+
10
10
+
const is$typed = _is$typed,
11
11
+
validate = _validate;
12
12
+
const id = "social.grain.gallery.getGallery";
13
13
+
14
14
+
export interface QueryParams {
15
15
+
/** The AT-URI of the gallery to return a hydrated view for. */
16
16
+
uri: string;
17
17
+
}
18
18
+
19
19
+
export type InputSchema = undefined;
20
20
+
export type OutputSchema = SocialGrainGalleryDefs.GalleryView;
21
21
+
export type HandlerInput = undefined;
22
22
+
23
23
+
export interface HandlerSuccess {
24
24
+
encoding: "application/json";
25
25
+
body: OutputSchema;
26
26
+
headers?: { [key: string]: string };
27
27
+
}
28
28
+
29
29
+
export interface HandlerError {
30
30
+
status: number;
31
31
+
message?: string;
32
32
+
}
33
33
+
34
34
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
35
35
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
36
36
+
auth: HA;
37
37
+
params: QueryParams;
38
38
+
input: HandlerInput;
39
39
+
req: express.Request;
40
40
+
res: express.Response;
41
41
+
resetRouteRateLimits: () => Promise<void>;
42
42
+
};
43
43
+
export type Handler<HA extends HandlerAuth = never> = (
44
44
+
ctx: HandlerReqCtx<HA>,
45
45
+
) => Promise<HandlerOutput> | HandlerOutput;
+51
__generated__/types/social/grain/gallery/getGalleryThread.ts
···
1
1
+
/**
2
2
+
* GENERATED CODE - DO NOT MODIFY
3
3
+
*/
4
4
+
import { HandlerAuth, HandlerPipeThrough } from "npm:@atproto/xrpc-server";
5
5
+
import express from "npm:express";
6
6
+
import { validate as _validate } from "../../../../lexicons.ts";
7
7
+
import { is$typed as _is$typed } from "../../../../util.ts";
8
8
+
import type * as SocialGrainCommentDefs from "../comment/defs.ts";
9
9
+
import type * as SocialGrainGalleryDefs from "./defs.ts";
10
10
+
11
11
+
const is$typed = _is$typed,
12
12
+
validate = _validate;
13
13
+
const id = "social.grain.gallery.getGalleryThread";
14
14
+
15
15
+
export interface QueryParams {
16
16
+
/** The AT-URI of the gallery to return a hydrated view and comments for. */
17
17
+
uri: string;
18
18
+
}
19
19
+
20
20
+
export type InputSchema = undefined;
21
21
+
22
22
+
export interface OutputSchema {
23
23
+
gallery: SocialGrainGalleryDefs.GalleryView;
24
24
+
comments: SocialGrainCommentDefs.CommentView[];
25
25
+
}
26
26
+
27
27
+
export type HandlerInput = undefined;
28
28
+
29
29
+
export interface HandlerSuccess {
30
30
+
encoding: "application/json";
31
31
+
body: OutputSchema;
32
32
+
headers?: { [key: string]: string };
33
33
+
}
34
34
+
35
35
+
export interface HandlerError {
36
36
+
status: number;
37
37
+
message?: string;
38
38
+
}
39
39
+
40
40
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
41
41
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
42
42
+
auth: HA;
43
43
+
params: QueryParams;
44
44
+
input: HandlerInput;
45
45
+
req: express.Request;
46
46
+
res: express.Response;
47
47
+
resetRouteRateLimits: () => Promise<void>;
48
48
+
};
49
49
+
export type Handler<HA extends HandlerAuth = never> = (
50
50
+
ctx: HandlerReqCtx<HA>,
51
51
+
) => Promise<HandlerOutput> | HandlerOutput;
+1
-1
deno.json
···
3
3
"$lexicon/": "./__generated__/",
4
4
"@atproto/api": "npm:@atproto/api@^0.15.16",
5
5
"@atproto/syntax": "npm:@atproto/syntax@^0.4.0",
6
6
-
"@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.41",
6
6
+
"@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.42",
7
7
"@std/http": "jsr:@std/http@^1.0.17",
8
8
"@std/path": "jsr:@std/path@^1.0.9",
9
9
"@tailwindcss/cli": "npm:@tailwindcss/cli@^4.1.4",
+72
-5
deno.lock
···
2
2
"version": "5",
3
3
"specifiers": {
4
4
"jsr:@bigmoves/atproto-oauth-client@0.2": "0.2.0",
5
5
-
"jsr:@bigmoves/bff@0.3.0-beta.41": "0.3.0-beta.41",
5
5
+
"jsr:@bigmoves/bff@0.3.0-beta.42": "0.3.0-beta.42",
6
6
"jsr:@deno/gfm@0.10": "0.10.0",
7
7
"jsr:@denosaurs/emoji@0.3": "0.3.1",
8
8
"jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1",
···
47
47
"npm:@atproto/oauth-client@~0.3.13": "0.3.22",
48
48
"npm:@atproto/oauth-types@~0.2.4": "0.2.8",
49
49
"npm:@atproto/syntax@0.4": "0.4.0",
50
50
-
"npm:@atproto/xrpc-server@*": "0.7.19",
50
50
+
"npm:@atproto/xrpc-server@*": "0.7.18",
51
51
"npm:@atproto/xrpc-server@0.7.18": "0.7.18",
52
52
"npm:@atproto/xrpc-server@0.7.19": "0.7.19",
53
53
"npm:@tailwindcss/cli@*": "4.1.9",
···
59
59
"npm:date-fns@^4.1.0": "4.1.0",
60
60
"npm:esbuild@~0.25.5": "0.25.5",
61
61
"npm:exifr@^7.1.3": "7.1.3",
62
62
+
"npm:express@*": "4.21.2",
62
63
"npm:github-slugger@2": "2.0.0",
63
64
"npm:he@^1.2.0": "1.2.0",
64
65
"npm:htmx.org@^1.9.12": "1.9.12",
65
66
"npm:hyperscript.org@~0.9.14": "0.9.14",
66
67
"npm:jose@5.9.6": "5.9.6",
68
68
+
"npm:jsonwebtoken@^9.0.2": "9.0.2",
67
69
"npm:katex@0.16": "0.16.22",
68
70
"npm:marked-alert@2": "2.1.2_marked@12.0.2",
69
71
"npm:marked-footnote@^1.2.0": "1.2.4_marked@12.0.2",
···
95
97
"npm:jose"
96
98
]
97
99
},
98
98
-
"@bigmoves/bff@0.3.0-beta.41": {
99
99
-
"integrity": "141414a26dcb44d6a08a8a259011e0a7ef01b104ff5664dafc380a6f0f9a22c0",
100
100
+
"@bigmoves/bff@0.3.0-beta.42": {
101
101
+
"integrity": "4866ae7f9e1a61e0abe8255f40e6ccba6d370a0ea4710cf061ebfcb5248ac688",
100
102
"dependencies": [
101
103
"jsr:@bigmoves/atproto-oauth-client",
102
104
"jsr:@std/assert@^1.0.13",
···
113
115
"npm:@atproto/syntax",
114
116
"npm:@atproto/xrpc-server@0.7.19",
115
117
"npm:clsx",
118
118
+
"npm:jsonwebtoken",
116
119
"npm:multiformats@^13.3.2",
117
120
"npm:preact",
118
121
"npm:preact-render-to-string",
···
995
998
"fill-range"
996
999
]
997
1000
},
1001
1001
+
"buffer-equal-constant-time@1.0.1": {
1002
1002
+
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
1003
1003
+
},
998
1004
"buffer-from@1.1.2": {
999
1005
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
1000
1006
},
···
1153
1159
"call-bind-apply-helpers",
1154
1160
"es-errors",
1155
1161
"gopd"
1162
1162
+
]
1163
1163
+
},
1164
1164
+
"ecdsa-sig-formatter@1.0.11": {
1165
1165
+
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
1166
1166
+
"dependencies": [
1167
1167
+
"safe-buffer"
1156
1168
]
1157
1169
},
1158
1170
"ee-first@1.1.1": {
···
1443
1455
"jose@5.9.6": {
1444
1456
"integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="
1445
1457
},
1458
1458
+
"jsonwebtoken@9.0.2": {
1459
1459
+
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
1460
1460
+
"dependencies": [
1461
1461
+
"jws",
1462
1462
+
"lodash.includes",
1463
1463
+
"lodash.isboolean",
1464
1464
+
"lodash.isinteger",
1465
1465
+
"lodash.isnumber",
1466
1466
+
"lodash.isplainobject",
1467
1467
+
"lodash.isstring",
1468
1468
+
"lodash.once",
1469
1469
+
"ms@2.1.3",
1470
1470
+
"semver"
1471
1471
+
]
1472
1472
+
},
1473
1473
+
"jwa@1.4.2": {
1474
1474
+
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
1475
1475
+
"dependencies": [
1476
1476
+
"buffer-equal-constant-time",
1477
1477
+
"ecdsa-sig-formatter",
1478
1478
+
"safe-buffer"
1479
1479
+
]
1480
1480
+
},
1481
1481
+
"jws@3.2.2": {
1482
1482
+
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
1483
1483
+
"dependencies": [
1484
1484
+
"jwa",
1485
1485
+
"safe-buffer"
1486
1486
+
]
1487
1487
+
},
1446
1488
"katex@0.16.22": {
1447
1489
"integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==",
1448
1490
"dependencies": [
···
1518
1560
"lightningcss-win32-x64-msvc"
1519
1561
]
1520
1562
},
1563
1563
+
"lodash.includes@4.3.0": {
1564
1564
+
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
1565
1565
+
},
1566
1566
+
"lodash.isboolean@3.0.3": {
1567
1567
+
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
1568
1568
+
},
1569
1569
+
"lodash.isinteger@4.0.4": {
1570
1570
+
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
1571
1571
+
},
1572
1572
+
"lodash.isnumber@3.0.3": {
1573
1573
+
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
1574
1574
+
},
1575
1575
+
"lodash.isplainobject@4.0.6": {
1576
1576
+
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
1577
1577
+
},
1578
1578
+
"lodash.isstring@4.0.1": {
1579
1579
+
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
1580
1580
+
},
1581
1581
+
"lodash.once@4.1.1": {
1582
1582
+
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
1583
1583
+
},
1521
1584
"lru-cache@10.4.3": {
1522
1585
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
1523
1586
},
···
1799
1862
"postcss"
1800
1863
]
1801
1864
},
1865
1865
+
"semver@7.7.2": {
1866
1866
+
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
1867
1867
+
"bin": true
1868
1868
+
},
1802
1869
"send@0.19.0": {
1803
1870
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
1804
1871
"dependencies": [
···
2033
2100
},
2034
2101
"workspace": {
2035
2102
"dependencies": [
2036
2036
-
"jsr:@bigmoves/bff@0.3.0-beta.41",
2103
2103
+
"jsr:@bigmoves/bff@0.3.0-beta.42",
2037
2104
"jsr:@std/http@^1.0.17",
2038
2105
"jsr:@std/path@^1.0.9",
2039
2106
"npm:@atproto/api@~0.15.16",
+37
lexicons/social/grain/actor/defs.json
···
28
28
"avatar": { "type": "string", "format": "uri" },
29
29
"createdAt": { "type": "string", "format": "datetime" }
30
30
}
31
31
+
},
32
32
+
"profileViewDetailed": {
33
33
+
"type": "object",
34
34
+
"required": ["did", "handle"],
35
35
+
"properties": {
36
36
+
"did": { "type": "string", "format": "did" },
37
37
+
"handle": { "type": "string", "format": "handle" },
38
38
+
"displayName": {
39
39
+
"type": "string",
40
40
+
"maxGraphemes": 64,
41
41
+
"maxLength": 640
42
42
+
},
43
43
+
"description": {
44
44
+
"type": "string",
45
45
+
"maxGraphemes": 256,
46
46
+
"maxLength": 2560
47
47
+
},
48
48
+
"avatar": { "type": "string", "format": "uri" },
49
49
+
"followersCount": { "type": "integer" },
50
50
+
"followsCount": { "type": "integer" },
51
51
+
"galleryCount": { "type": "integer" },
52
52
+
"indexedAt": { "type": "string", "format": "datetime" },
53
53
+
"createdAt": { "type": "string", "format": "datetime" },
54
54
+
"viewer": { "type": "ref", "ref": "#viewerState" },
55
55
+
"labels": {
56
56
+
"type": "array",
57
57
+
"items": { "type": "ref", "ref": "com.atproto.label.defs#label" }
58
58
+
}
59
59
+
}
60
60
+
},
61
61
+
"viewerState": {
62
62
+
"type": "object",
63
63
+
"description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.",
64
64
+
"properties": {
65
65
+
"following": { "type": "string", "format": "at-uri" },
66
66
+
"followedBy": { "type": "string", "format": "at-uri" }
67
67
+
}
31
68
}
32
69
}
33
70
}
+28
lexicons/social/grain/actor/getProfile.json
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "social.grain.actor.getProfile",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "query",
7
7
+
"description": "Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.",
8
8
+
"parameters": {
9
9
+
"type": "params",
10
10
+
"required": ["actor"],
11
11
+
"properties": {
12
12
+
"actor": {
13
13
+
"type": "string",
14
14
+
"format": "at-identifier",
15
15
+
"description": "Handle or DID of account to fetch profile of."
16
16
+
}
17
17
+
}
18
18
+
},
19
19
+
"output": {
20
20
+
"encoding": "application/json",
21
21
+
"schema": {
22
22
+
"type": "ref",
23
23
+
"ref": "social.grain.actor.defs#profileViewDetailed"
24
24
+
}
25
25
+
}
26
26
+
}
27
27
+
}
28
28
+
}
+43
lexicons/social/grain/feed/getTimeline.json
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "social.grain.feed.getTimeline",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "query",
7
7
+
"description": "Get a view of the requesting account's home timeline.",
8
8
+
"parameters": {
9
9
+
"type": "params",
10
10
+
"properties": {
11
11
+
"algorithm": {
12
12
+
"type": "string",
13
13
+
"description": "Variant 'algorithm' for timeline. Implementation-specific."
14
14
+
},
15
15
+
"limit": {
16
16
+
"type": "integer",
17
17
+
"minimum": 1,
18
18
+
"maximum": 100,
19
19
+
"default": 50
20
20
+
},
21
21
+
"cursor": { "type": "string" }
22
22
+
}
23
23
+
},
24
24
+
"output": {
25
25
+
"encoding": "application/json",
26
26
+
"schema": {
27
27
+
"type": "object",
28
28
+
"required": ["feed"],
29
29
+
"properties": {
30
30
+
"cursor": { "type": "string" },
31
31
+
"feed": {
32
32
+
"type": "array",
33
33
+
"items": {
34
34
+
"type": "ref",
35
35
+
"ref": "social.grain.gallery.defs#galleryView"
36
36
+
}
37
37
+
}
38
38
+
}
39
39
+
}
40
40
+
}
41
41
+
}
42
42
+
}
43
43
+
}
+42
lexicons/social/grain/gallery/getActorGalleries.json
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "social.grain.gallery.getActorGalleries",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "query",
7
7
+
"description": "Get a view of an actor's galleries. Does not require auth.",
8
8
+
"parameters": {
9
9
+
"type": "params",
10
10
+
"required": ["actor"],
11
11
+
"properties": {
12
12
+
"actor": { "type": "string", "format": "at-identifier" },
13
13
+
"limit": {
14
14
+
"type": "integer",
15
15
+
"minimum": 1,
16
16
+
"maximum": 100,
17
17
+
"default": 50
18
18
+
},
19
19
+
"cursor": { "type": "string" }
20
20
+
}
21
21
+
},
22
22
+
"output": {
23
23
+
"encoding": "application/json",
24
24
+
"schema": {
25
25
+
"type": "object",
26
26
+
"required": ["items"],
27
27
+
"properties": {
28
28
+
"cursor": { "type": "string" },
29
29
+
"items": {
30
30
+
"type": "array",
31
31
+
"items": {
32
32
+
"type": "ref",
33
33
+
"ref": "social.grain.gallery.defs#galleryView"
34
34
+
}
35
35
+
}
36
36
+
}
37
37
+
}
38
38
+
},
39
39
+
"errors": [{ "name": "BlockedActor" }, { "name": "BlockedByActor" }]
40
40
+
}
41
41
+
}
42
42
+
}
+28
lexicons/social/grain/gallery/getGallery.json
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "social.grain.gallery.getGallery",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "query",
7
7
+
"description": "Gets a hydrated gallery view for a specified gallery AT-URI.",
8
8
+
"parameters": {
9
9
+
"type": "params",
10
10
+
"required": ["uri"],
11
11
+
"properties": {
12
12
+
"uri": {
13
13
+
"type": "string",
14
14
+
"description": "The AT-URI of the gallery to return a hydrated view for.",
15
15
+
"format": "at-uri"
16
16
+
}
17
17
+
}
18
18
+
},
19
19
+
"output": {
20
20
+
"encoding": "application/json",
21
21
+
"schema": {
22
22
+
"type": "ref",
23
23
+
"ref": "social.grain.gallery.defs#galleryView"
24
24
+
}
25
25
+
}
26
26
+
}
27
27
+
}
28
28
+
}
+41
lexicons/social/grain/gallery/getGalleryThread.json
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "social.grain.gallery.getGalleryThread",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "query",
7
7
+
"description": "Gets a hydrated gallery view and its comments for a specified gallery AT-URI.",
8
8
+
"parameters": {
9
9
+
"type": "params",
10
10
+
"required": ["uri"],
11
11
+
"properties": {
12
12
+
"uri": {
13
13
+
"type": "string",
14
14
+
"description": "The AT-URI of the gallery to return a hydrated view and comments for.",
15
15
+
"format": "at-uri"
16
16
+
}
17
17
+
}
18
18
+
},
19
19
+
"output": {
20
20
+
"encoding": "application/json",
21
21
+
"schema": {
22
22
+
"type": "object",
23
23
+
"required": ["gallery", "comments"],
24
24
+
"properties": {
25
25
+
"gallery": {
26
26
+
"type": "ref",
27
27
+
"ref": "social.grain.gallery.defs#galleryView"
28
28
+
},
29
29
+
"comments": {
30
30
+
"type": "array",
31
31
+
"items": {
32
32
+
"type": "ref",
33
33
+
"ref": "social.grain.comment.defs#commentView"
34
34
+
}
35
35
+
}
36
36
+
}
37
37
+
}
38
38
+
}
39
39
+
}
40
40
+
}
41
41
+
}
+142
src/api/mod.ts
···
1
1
+
import {
2
2
+
OutputSchema as GetProfileOutputSchema,
3
3
+
QueryParams as GetProfileQueryParams,
4
4
+
} from "$lexicon/types/social/grain/actor/getProfile.ts";
5
5
+
import {
6
6
+
OutputSchema as GetTimelineOutputSchema,
7
7
+
} from "$lexicon/types/social/grain/feed/getTimeline.ts";
8
8
+
import {
9
9
+
OutputSchema as GetActorGalleriesOutputSchema,
10
10
+
QueryParams as GetActorGalleriesQueryParams,
11
11
+
} from "$lexicon/types/social/grain/gallery/getActorGalleries.ts";
12
12
+
import {
13
13
+
OutputSchema as GetGalleryOutputSchema,
14
14
+
QueryParams as GetGalleryQueryParams,
15
15
+
} from "$lexicon/types/social/grain/gallery/getGallery.ts";
16
16
+
import {
17
17
+
OutputSchema as GetGalleryThreadOutputSchema,
18
18
+
QueryParams as GetGalleryThreadQueryParams,
19
19
+
} from "$lexicon/types/social/grain/gallery/getGalleryThread.ts";
20
20
+
import { AtUri } from "@atproto/syntax";
21
21
+
import { BffMiddleware, OAUTH_ROUTES, route } from "@bigmoves/bff";
22
22
+
import { getActorGalleries, getActorProfileDetailed } from "../lib/actor.ts";
23
23
+
import { BadRequestError } from "../lib/errors.ts";
24
24
+
import { getGallery } from "../lib/gallery.ts";
25
25
+
import { getTimeline } from "../lib/timeline.ts";
26
26
+
import { getGalleryComments } from "../modules/comments.tsx";
27
27
+
28
28
+
export const middlewares: BffMiddleware[] = [
29
29
+
async (req, ctx) => {
30
30
+
const url = new URL(req.url);
31
31
+
const { pathname } = url;
32
32
+
33
33
+
if (pathname === OAUTH_ROUTES.tokenCallback) {
34
34
+
const token = url.searchParams.get("token") ?? undefined;
35
35
+
if (!token) {
36
36
+
throw new BadRequestError("Missing token parameter");
37
37
+
}
38
38
+
return ctx.redirect(`grainflutter://auth/callback?token=${token}`);
39
39
+
}
40
40
+
41
41
+
return ctx.next();
42
42
+
},
43
43
+
route("/oauth/session", (_req, _params, ctx) => {
44
44
+
if (!ctx.currentUser) {
45
45
+
return ctx.json("Unauthorized", 401);
46
46
+
}
47
47
+
const did = ctx.currentUser.did;
48
48
+
const profile = getActorProfileDetailed(did, ctx);
49
49
+
if (!profile) {
50
50
+
return ctx.json("Profile not found", 404);
51
51
+
}
52
52
+
return ctx.json(profile);
53
53
+
}),
54
54
+
route("/xrpc/social.grain.actor.getProfile", (req, _params, ctx) => {
55
55
+
const url = new URL(req.url);
56
56
+
const { actor } = getProfileQueryParams(url);
57
57
+
const profile = getActorProfileDetailed(actor, ctx);
58
58
+
return ctx.json(profile as GetProfileOutputSchema);
59
59
+
}),
60
60
+
route("/xrpc/social.grain.gallery.getActorGalleries", (req, _params, ctx) => {
61
61
+
const url = new URL(req.url);
62
62
+
const { actor } = getActorGalleriesQueryParams(url);
63
63
+
const galleries = getActorGalleries(actor, ctx);
64
64
+
return ctx.json({ items: galleries } as GetActorGalleriesOutputSchema);
65
65
+
}),
66
66
+
route("/xrpc/social.grain.gallery.getGallery", (req, _params, ctx) => {
67
67
+
const url = new URL(req.url);
68
68
+
const { uri } = getGalleryQueryParams(url);
69
69
+
const atUri = new AtUri(uri);
70
70
+
const did = atUri.hostname;
71
71
+
const rkey = atUri.rkey;
72
72
+
const gallery = getGallery(did, rkey, ctx);
73
73
+
if (!gallery) {
74
74
+
return ctx.json("Gallery not found", 404);
75
75
+
}
76
76
+
return ctx.json(gallery as GetGalleryOutputSchema);
77
77
+
}),
78
78
+
route("/xrpc/social.grain.gallery.getGalleryThread", (req, _params, ctx) => {
79
79
+
const url = new URL(req.url);
80
80
+
const { uri } = getGalleryThreadQueryParams(url);
81
81
+
const atUri = new AtUri(uri);
82
82
+
const did = atUri.hostname;
83
83
+
const rkey = atUri.rkey;
84
84
+
const gallery = getGallery(did, rkey, ctx);
85
85
+
if (!gallery) {
86
86
+
return ctx.json("Gallery not found", 404);
87
87
+
}
88
88
+
const comments = getGalleryComments(uri, ctx);
89
89
+
return ctx.json({ gallery, comments } as GetGalleryThreadOutputSchema);
90
90
+
}),
91
91
+
route("/xrpc/social.grain.feed.getTimeline", async (_req, _params, ctx) => {
92
92
+
// const url = new URL(req.url);
93
93
+
// const { algorithm, limit, cursor } = getTimelineQueryParams(url);
94
94
+
const items = await getTimeline(
95
95
+
ctx,
96
96
+
"timeline",
97
97
+
"grain",
98
98
+
);
99
99
+
return ctx.json(
100
100
+
{ feed: items.map((i) => i.gallery) } as GetTimelineOutputSchema,
101
101
+
);
102
102
+
}),
103
103
+
];
104
104
+
105
105
+
function getProfileQueryParams(url: URL): GetProfileQueryParams {
106
106
+
const actor = url.searchParams.get("actor");
107
107
+
if (!actor) throw new BadRequestError("Missing actor parameter");
108
108
+
return { actor };
109
109
+
}
110
110
+
111
111
+
function getActorGalleriesQueryParams(url: URL): GetActorGalleriesQueryParams {
112
112
+
const actor = url.searchParams.get("actor");
113
113
+
if (!actor) throw new BadRequestError("Missing actor parameter");
114
114
+
const limit = parseInt(url.searchParams.get("limit") ?? "50", 10);
115
115
+
if (isNaN(limit) || limit <= 0) {
116
116
+
throw new BadRequestError("Invalid limit parameter");
117
117
+
}
118
118
+
const cursor = url.searchParams.get("cursor") ?? undefined;
119
119
+
return { actor, limit, cursor };
120
120
+
}
121
121
+
122
122
+
function getGalleryQueryParams(url: URL): GetGalleryQueryParams {
123
123
+
const uri = url.searchParams.get("uri");
124
124
+
if (!uri) throw new BadRequestError("Missing uri parameter");
125
125
+
return { uri };
126
126
+
}
127
127
+
128
128
+
function getGalleryThreadQueryParams(url: URL): GetGalleryThreadQueryParams {
129
129
+
const uri = url.searchParams.get("uri");
130
130
+
if (!uri) throw new BadRequestError("Missing uri parameter");
131
131
+
return { uri };
132
132
+
}
133
133
+
134
134
+
// function getTimelineQueryParams(url: URL): GetTimelineQueryParams {
135
135
+
// const algorithm = url.searchParams.get("algorithm") ?? undefined;
136
136
+
// const limit = parseInt(url.searchParams.get("limit") ?? "50", 10);
137
137
+
// if (isNaN(limit) || limit <= 0) {
138
138
+
// throw new BadRequestError("Invalid limit parameter");
139
139
+
// }
140
140
+
// const cursor = url.searchParams.get("cursor") ?? undefined;
141
141
+
// return { algorithm, limit, cursor };
142
142
+
// }
+51
-2
src/lib/actor.ts
···
1
1
import { Record as BskyProfile } from "$lexicon/types/app/bsky/actor/profile.ts";
2
2
import { Label } from "$lexicon/types/com/atproto/label/defs.ts";
3
3
import { Record as TangledProfile } from "$lexicon/types/sh/tangled/actor/profile.ts";
4
4
-
import { ProfileView } from "$lexicon/types/social/grain/actor/defs.ts";
4
4
+
import {
5
5
+
ProfileView,
6
6
+
ProfileViewDetailed,
7
7
+
} from "$lexicon/types/social/grain/actor/defs.ts";
5
8
import { Record as GrainProfile } from "$lexicon/types/social/grain/actor/profile.ts";
6
9
import { Record as Favorite } from "$lexicon/types/social/grain/favorite.ts";
7
10
import { Record as Gallery } from "$lexicon/types/social/grain/gallery.ts";
···
9
12
import { Record as PhotoExif } from "$lexicon/types/social/grain/photo/exif.ts";
10
13
import { Un$Typed } from "$lexicon/util.ts";
11
14
import { BffContext, WithBffMeta } from "@bigmoves/bff";
12
12
-
import { galleryToView, getGalleryItemsAndPhotos } from "./gallery.ts";
15
15
+
import { getFollowersCount, getFollowsCount } from "./follow.ts";
16
16
+
import {
17
17
+
galleryToView,
18
18
+
getGalleryCount,
19
19
+
getGalleryItemsAndPhotos,
20
20
+
} from "./gallery.ts";
13
21
import { photoToView, photoUrl } from "./photo.ts";
14
22
import type { SocialNetwork } from "./timeline.ts";
15
23
···
22
30
return profileRecord ? profileToView(profileRecord, actor.handle) : null;
23
31
}
24
32
33
33
+
export function getActorProfileDetailed(did: string, ctx: BffContext) {
34
34
+
const actor = ctx.indexService.getActor(did);
35
35
+
if (!actor) return null;
36
36
+
const profileRecord = ctx.indexService.getRecord<WithBffMeta<GrainProfile>>(
37
37
+
`at://${did}/social.grain.actor.profile/self`,
38
38
+
);
39
39
+
const followersCount = getFollowersCount(did, ctx);
40
40
+
const followsCount = getFollowsCount(did, ctx);
41
41
+
const galleryCount = getGalleryCount(did, ctx);
42
42
+
return profileRecord
43
43
+
? profileDetailedToView(
44
44
+
profileRecord,
45
45
+
actor.handle,
46
46
+
followersCount,
47
47
+
followsCount,
48
48
+
galleryCount,
49
49
+
)
50
50
+
: null;
51
51
+
}
52
52
+
25
53
export function profileToView(
26
54
record: WithBffMeta<GrainProfile>,
27
55
handle: string,
···
34
62
avatar: record?.avatar
35
63
? photoUrl(record.did, record.avatar.ref.toString(), "thumbnail")
36
64
: undefined,
65
65
+
};
66
66
+
}
67
67
+
68
68
+
export function profileDetailedToView(
69
69
+
record: WithBffMeta<GrainProfile>,
70
70
+
handle: string,
71
71
+
followersCount: number,
72
72
+
followsCount: number,
73
73
+
galleryCount: number,
74
74
+
): Un$Typed<ProfileViewDetailed> {
75
75
+
return {
76
76
+
did: record.did,
77
77
+
handle,
78
78
+
displayName: record.displayName,
79
79
+
description: record.description,
80
80
+
avatar: record?.avatar
81
81
+
? photoUrl(record.did, record.avatar.ref.toString(), "thumbnail")
82
82
+
: undefined,
83
83
+
followersCount,
84
84
+
followsCount,
85
85
+
galleryCount,
37
86
};
38
87
}
39
88
+32
src/lib/follow.ts
···
90
90
profile != null
91
91
);
92
92
}
93
93
+
94
94
+
export function getFollowersCount(
95
95
+
followeeDid: string,
96
96
+
ctx: BffContext,
97
97
+
): number {
98
98
+
return ctx.indexService.countRecords(
99
99
+
"social.grain.graph.follow",
100
100
+
{
101
101
+
orderBy: [{ field: "createdAt", direction: "desc" }],
102
102
+
where: [{
103
103
+
field: "subject",
104
104
+
equals: followeeDid,
105
105
+
}],
106
106
+
},
107
107
+
);
108
108
+
}
109
109
+
110
110
+
export function getFollowsCount(
111
111
+
followerDid: string,
112
112
+
ctx: BffContext,
113
113
+
): number {
114
114
+
return ctx.indexService.countRecords(
115
115
+
"social.grain.graph.follow",
116
116
+
{
117
117
+
orderBy: [{ field: "createdAt", direction: "desc" }],
118
118
+
where: [{
119
119
+
field: "did",
120
120
+
equals: followerDid,
121
121
+
}],
122
122
+
},
123
123
+
);
124
124
+
}
+9
src/lib/gallery.ts
···
429
429
})
430
430
.filter((g): g is ReturnType<typeof galleryToView> => g !== null);
431
431
}
432
432
+
433
433
+
export function getGalleryCount(
434
434
+
userDid: string,
435
435
+
ctx: BffContext,
436
436
+
): number {
437
437
+
return ctx.indexService.countRecords("social.grain.gallery", {
438
438
+
where: [{ field: "did", equals: userDid }],
439
439
+
});
440
440
+
}
+2
src/main.tsx
···
1
1
import { lexicons } from "$lexicon/lexicons.ts";
2
2
import { bff, oauth, route } from "@bigmoves/bff";
3
3
+
import { middlewares as xrpcApi } from "./api/mod.ts";
3
4
import { Root } from "./app.tsx";
4
5
import { LoginPage } from "./components/LoginPage.tsx";
5
6
import { PDS_HOST_URL } from "./env.ts";
···
55
56
rootElement: Root,
56
57
onError,
57
58
middlewares: [
59
59
+
...xrpcApi,
58
60
appStateMiddleware,
59
61
oauth({
60
62
onSignedIn,
+4
-1
src/modules/comments.tsx
···
530
530
}, []);
531
531
}
532
532
533
533
-
function getGalleryComments(uri: string, ctx: BffContext): CommentView[] {
533
533
+
export function getGalleryComments(
534
534
+
uri: string,
535
535
+
ctx: BffContext,
536
536
+
): CommentView[] {
534
537
const { items: comments } = ctx.indexService.getRecords<WithBffMeta<Comment>>(
535
538
"social.grain.comment",
536
539
{