@@ -7,12 +7,18 @@ import {
77 Param ,
88 Req ,
99 Logger ,
10+ HttpException ,
11+ HttpStatus ,
12+ NotFoundException ,
1013} from '@nestjs/common' ;
1114import { BlueskyService } from './bluesky.service' ;
1215import { JWTAuthGuard } from '../auth/auth.guard' ;
1316import { ApiTags , ApiOperation , ApiBearerAuth } from '@nestjs/swagger' ;
1417import { UserEntity } from '../user/infrastructure/persistence/relational/entities/user.entity' ;
1518import { AuthUser } from '../core/decorators/auth-user.decorator' ;
19+ import { RoleEnum } from '../role/role.enum' ;
20+ import { Public } from '../auth/decorators/public.decorator' ;
21+ import { UserService } from '../user/user.service' ;
1622
1723@ApiTags ( 'Bluesky' )
1824@Controller ( 'bluesky' )
@@ -21,7 +27,10 @@ import { AuthUser } from '../core/decorators/auth-user.decorator';
2127export class BlueskyController {
2228 private readonly logger = new Logger ( BlueskyController . name ) ;
2329
24- constructor ( private readonly blueskyService : BlueskyService ) { }
30+ constructor (
31+ private readonly blueskyService : BlueskyService ,
32+ private readonly userService : UserService ,
33+ ) { }
2534
2635 @Post ( 'connect' )
2736 @ApiOperation ( { summary : 'Enable Bluesky event source' } )
@@ -41,9 +50,108 @@ export class BlueskyController {
4150 return this . blueskyService . getConnectionStatus ( user ) ;
4251 }
4352
53+ @Get ( 'profile' )
54+ @ApiOperation ( {
55+ summary : 'Get enhanced ATProtocol profile for the current user' ,
56+ } )
57+ async getCurrentUserProfile ( @AuthUser ( ) user : UserEntity , @Req ( ) req ) {
58+ try {
59+ if (
60+ ! user . preferences ?. bluesky ?. did &&
61+ ! user . preferences ?. bluesky ?. handle
62+ ) {
63+ return {
64+ connected : false ,
65+ message : 'No ATProtocol account connected' ,
66+ } ;
67+ }
68+
69+ return await this . blueskyService . getEnhancedProfile ( user , req . tenantId ) ;
70+ } catch ( error ) {
71+ this . logger . error ( 'Error fetching current user ATProtocol profile' , {
72+ error : error . message ,
73+ stack : error . stack ,
74+ userId : user . id ,
75+ } ) ;
76+
77+ throw new HttpException (
78+ error . message || 'Failed to fetch profile' ,
79+ HttpStatus . INTERNAL_SERVER_ERROR ,
80+ ) ;
81+ }
82+ }
83+
84+ @Public ( )
85+ @Get ( 'profile/:identifier' )
86+ @ApiOperation ( { summary : 'Get public ATProtocol profile by DID or handle' } )
87+ async getPublicProfile ( @Param ( 'identifier' ) identifier : string ) {
88+ try {
89+ this . logger . debug ( `Public profile lookup for: ${ identifier } ` ) ;
90+ return await this . blueskyService . getPublicProfile ( identifier ) ;
91+ } catch ( error ) {
92+ this . logger . error ( 'Error fetching public ATProtocol profile' , {
93+ error : error . message ,
94+ stack : error . stack ,
95+ identifier,
96+ } ) ;
97+
98+ throw new HttpException (
99+ error . message || 'Failed to fetch profile' ,
100+ HttpStatus . NOT_FOUND ,
101+ ) ;
102+ }
103+ }
104+
105+ @Get ( 'user-profile/:slug' )
106+ @ApiOperation ( {
107+ summary : 'Get ATProtocol profile for a specific OpenMeet user by slug' ,
108+ } )
109+ async getUserProfile (
110+ @Param ( 'slug' ) slug : string ,
111+ @Req ( ) req ,
112+ @AuthUser ( ) currentUser : UserEntity ,
113+ ) {
114+ try {
115+ const user = await this . userService . findBySlug ( slug , req . tenantId ) ;
116+
117+ if ( ! user ) {
118+ throw new NotFoundException ( `User with slug ${ slug } not found` ) ;
119+ }
120+
121+ // Only allow viewing full profile details if admin or the user themselves
122+ const isAdmin = currentUser . role ?. name === RoleEnum . Admin ;
123+ const isSelf = currentUser . id === user . id ;
124+
125+ if ( ! isAdmin && ! isSelf ) {
126+ return {
127+ message : 'User profile basics only' ,
128+ // Return minimal public info
129+ handle : user . preferences ?. bluesky ?. handle ,
130+ connected : ! ! user . preferences ?. bluesky ?. connected ,
131+ } ;
132+ }
133+
134+ return await this . blueskyService . getEnhancedProfile ( user , req . tenantId ) ;
135+ } catch ( error ) {
136+ this . logger . error ( 'Error fetching ATProtocol profile for user' , {
137+ error : error . message ,
138+ stack : error . stack ,
139+ slug,
140+ } ) ;
141+
142+ if ( error instanceof NotFoundException ) {
143+ throw error ;
144+ }
145+
146+ throw new HttpException (
147+ error . message || 'Failed to fetch profile' ,
148+ HttpStatus . INTERNAL_SERVER_ERROR ,
149+ ) ;
150+ }
151+ }
152+
44153 @Get ( 'events/:did' )
45154 @ApiOperation ( { summary : 'List Bluesky events' } )
46- @UseGuards ( JWTAuthGuard )
47155 async listEvents ( @Req ( ) req , @Param ( 'did' ) did : string ) {
48156 return await this . blueskyService . listEvents ( did , req . tenantId ) ;
49157 }
@@ -61,4 +169,28 @@ export class BlueskyController {
61169 req . tenantId ,
62170 ) ;
63171 }
172+
173+ @Post ( 'session/reset/:did' )
174+ @ApiOperation ( { summary : 'Reset a Bluesky session' } )
175+ async resetSession (
176+ @Param ( 'did' ) did : string ,
177+ @Req ( ) req ,
178+ @AuthUser ( ) user : UserEntity ,
179+ ) {
180+ this . logger . log ( `Request to reset Bluesky session for DID: ${ did } ` ) ;
181+
182+ // Check if user owns this DID or has admin permissions
183+ const isOwner = user . preferences ?. bluesky ?. did === did ;
184+ const isAdmin = user . role ?. name === RoleEnum . Admin ;
185+
186+ if ( ! isOwner && ! isAdmin ) {
187+ return {
188+ success : false ,
189+ message : 'You do not have permission to reset this session' ,
190+ } ;
191+ }
192+
193+ // Use the service method to reset the session
194+ return await this . blueskyService . resetSession ( did , req . tenantId ) ;
195+ }
64196}
0 commit comments