• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

karanshukla / navyfragen-app / 28335518066

28 Jun 2026 08:48PM UTC coverage: 98.192% (-0.4%) from 98.55%
28335518066

push

github

web-flow
Merge pull request #189 from karanshukla/claude/bluesky-handle-autocomplete-car4lv

Add Bluesky handle autocomplete on the login page

1923 of 2006 branches covered (95.86%)

Branch coverage included in aggregate %.

31 of 72 new or added lines in 3 files covered. (43.06%)

7 existing lines in 1 file now uncovered.

7199 of 7284 relevant lines covered (98.83%)

6.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.84
/server/src/controllers/profile-controller.ts
1
/* v8 ignore start */
1✔
2
import express from "express";
1✔
3
import { param, query } from "express-validator";
1✔
4
import { Logger } from "pino";
1✔
5

1✔
6
import { ProfileService } from "../services/profile-service";
1✔
7

1✔
8
import type { AppContext } from "../index";
1✔
9

1✔
10
import { initializeAgentFromSession } from "#/auth/session-agent";
1✔
11

1✔
12
const BOT_DID = "did:plc:3d4awubjiftylwrhhyp5vl7i";
1✔
13

1✔
14
export class ProfileController {
1✔
15
  constructor(
1✔
16
    private profileService: ProfileService,
1✔
17
    private logger: Logger,
1✔
18
    private ctx: AppContext
1✔
19
  ) {}
1✔
20
  /* v8 ignore stop */
1✔
21

16✔
22
  /**
16✔
23
   * Validation for public profile request
16✔
24
   */
16✔
25
  validateGetPublicProfile = [param("did").isString().notEmpty().withMessage("DID required")];
16✔
26

16✔
27
  /**
16✔
28
   * Get public profile for a DID
16✔
29
   */
16✔
30
  getPublicProfile = async (
16✔
31
    req: express.Request,
3✔
32
    res: express.Response
3✔
33
  ): Promise<express.Response> => {
3✔
34
    const did = req.params.did;
3✔
35

3✔
36
    try {
3✔
37
      const profileData = await this.profileService.getPublicProfile(did);
3✔
38
      return res.json(profileData);
3✔
39
    } catch (err: any) {
3✔
40
      if (err.message === "Profile not found") {
2✔
41
        return res.status(404).json({ error: "Profile not found" });
1✔
42
      }
1✔
43
      this.logger.error({ err, did }, "Failed to fetch public profile");
1✔
44
      return res.status(500).json({ error: "Failed to fetch profile" });
1✔
45
    }
1✔
46
  };
3✔
47

16✔
48
  /**
16✔
49
   * Validation for checking if user exists
16✔
50
   */
16✔
51
  validateUserExists = [param("did").isString().notEmpty().withMessage("DID required")];
16✔
52

16✔
53
  /**
16✔
54
   * Check if a user exists in the database
16✔
55
   */
16✔
56
  checkUserExists = async (
16✔
57
    req: express.Request,
2✔
58
    res: express.Response
2✔
59
  ): Promise<express.Response> => {
2✔
60
    const did = req.params.did;
2✔
61

2✔
62
    try {
2✔
63
      const exists = await this.profileService.checkUserExists(did);
2✔
64
      return res.json({ exists, did });
2✔
65
    } catch (err) {
1✔
66
      this.logger.error({ err, did }, "Failed to check user existence");
1✔
67
      return res.status(500).json({ error: "Failed to check user existence" });
1✔
68
    }
1✔
69
  };
2✔
70

16✔
71
  /**
16✔
72
   * Get the logged-in user's Bluesky follows who are also on Navyfragen
16✔
73
   */
16✔
74
  getFriends = async (req: express.Request, res: express.Response): Promise<express.Response> => {
16✔
75
    const userDid = req.session?.did;
4✔
76
    if (!userDid) {
4✔
77
      return res.status(403).json({ error: "Not authenticated" });
1✔
78
    }
1✔
79

4✔
80
    const agent = await initializeAgentFromSession(req, this.ctx);
4✔
81
    if (!agent) {
4✔
82
      return res.status(401).json({ error: "Session expired" });
1✔
83
    }
1✔
84

4✔
85
    try {
4✔
86
      const result = await this.profileService.getFriendsOnApp(userDid, agent);
2✔
87
      return res.json(result);
4✔
88
    } catch (err) {
1✔
89
      this.logger.error({ err, did: userDid }, "Failed to fetch friends on app");
1✔
90
      return res.status(500).json({ error: "Failed to fetch friends" });
1✔
91
    }
1✔
92
  };
4✔
93

16✔
94
  /**
16✔
95
   * Check if the logged-in user follows the notification bot
16✔
96
   */
16✔
97
  checkBotFollow = async (
16✔
98
    req: express.Request,
4✔
99
    res: express.Response
4✔
100
  ): Promise<express.Response> => {
4✔
101
    const userDid = req.session?.did;
4✔
102
    if (!userDid) {
4✔
103
      return res.status(403).json({ error: "Not authenticated" });
1✔
104
    }
1✔
105

4✔
106
    const agent = await initializeAgentFromSession(req, this.ctx);
4✔
107
    if (!agent) {
4✔
108
      return res.status(401).json({ error: "Session expired" });
1✔
109
    }
1✔
110

4✔
111
    try {
4✔
112
      const following = await this.profileService.checkFollowsBot(agent, BOT_DID);
2✔
113
      return res.json({ following });
4✔
114
    } catch (err) {
1✔
115
      this.logger.error({ err, did: userDid }, "Failed to check bot follow status");
1✔
116
      return res.status(500).json({ error: "Failed to check bot follow status" });
1✔
117
    }
1✔
118
  };
4✔
119

16✔
120
  validateHandlePDS = [param("handle").isString().notEmpty().withMessage("Handle required")];
16✔
121

16✔
122
  getHandlePDS = async (req: express.Request, res: express.Response): Promise<express.Response> => {
16✔
NEW
123
    const handle = req.params.handle;
×
NEW
124
    try {
×
NEW
125
      const did = await this.ctx.resolver.resolveHandleToDid(handle);
×
NEW
126
      if (!did) return res.status(404).json({ error: "Handle not found" });
×
NEW
127
      const atprotoData = await this.ctx.idResolver.did.resolveAtprotoData(did);
×
NEW
128
      const pdsUrl = new URL(atprotoData.pds);
×
NEW
129
      return res.json({ pds: pdsUrl.hostname });
×
NEW
130
    } catch (err) {
×
NEW
131
      this.logger.error({ err, handle }, "Failed to resolve PDS for handle");
×
NEW
132
      return res.status(500).json({ error: "Failed to resolve PDS" });
×
NEW
133
    }
×
NEW
134
  };
×
135

16✔
136
  validateSearchHandles = [
16✔
137
    query("q").isString().notEmpty().isLength({ max: 64 }).withMessage("Query required"),
16✔
138
  ];
16✔
139

16✔
140
  searchHandles = async (
16✔
NEW
141
    req: express.Request,
×
NEW
142
    res: express.Response
×
NEW
143
  ): Promise<express.Response> => {
×
NEW
144
    const q = req.query.q as string;
×
NEW
145
    try {
×
NEW
146
      const actors = await this.profileService.searchActorsTypeahead(q);
×
NEW
147
      return res.json({ actors });
×
NEW
148
    } catch (err) {
×
NEW
149
      this.logger.error({ err }, "Failed to search handles");
×
NEW
150
      return res.status(500).json({ error: "Failed to search handles" });
×
NEW
151
    }
×
NEW
152
  };
×
153

16✔
154
  /**
16✔
155
   * Validation for handle resolution
16✔
156
   */
16✔
157
  validateResolveHandle = [param("handle").isString().notEmpty().withMessage("Handle required")];
16✔
158

16✔
159
  /**
16✔
160
   * Resolve a handle to a DID
16✔
161
   */
16✔
162
  resolveHandle = async (
16✔
163
    req: express.Request,
3✔
164
    res: express.Response
3✔
165
  ): Promise<express.Response> => {
3✔
166
    const handle = req.params.handle;
3✔
167

3✔
168
    try {
3✔
169
      const did = await this.profileService.resolveHandleToDid(handle);
3✔
170
      return res.json({ did });
3✔
171
    } catch (err: any) {
3✔
172
      if (err.message === "Handle not found") {
2✔
173
        return res.status(404).json({ error: "Handle not found" });
1✔
174
      }
1✔
175
      this.logger.error({ err, handle }, "Failed to resolve handle");
1✔
176
      return res.status(500).json({ error: "Failed to resolve handle" });
1✔
177
    }
1✔
178
  };
3✔
179
  /* v8 ignore next 1 */
1✔
180
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc