Skip to content

Commit 542e62e

Browse files
committed
add tests, profile changes
1 parent 28a789b commit 542e62e

File tree

6 files changed

+980
-49
lines changed

6 files changed

+980
-49
lines changed

package-lock.json

Lines changed: 67 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
},
5353
"dependencies": {
5454
"@atproto/api": "^0.13.31",
55+
"@atproto/identity": "^0.4.7",
5556
"@atproto/jwk-jose": "^0.1.2",
5657
"@atproto/oauth-client-node": "^0.2.3",
5758
"@aws-sdk/client-s3": "3.651.1",

src/bluesky/bluesky.controller.ts

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@ import {
77
Param,
88
Req,
99
Logger,
10+
HttpException,
11+
HttpStatus,
12+
NotFoundException,
1013
} from '@nestjs/common';
1114
import { BlueskyService } from './bluesky.service';
1215
import { JWTAuthGuard } from '../auth/auth.guard';
1316
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
1417
import { UserEntity } from '../user/infrastructure/persistence/relational/entities/user.entity';
1518
import { 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';
2127
export 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

Comments
 (0)