@stackoverflow/backstage-plugin-stack-overflow-teams-backend 1.5.0 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,23 @@
1
- # Stack Overflow Teams Backend Plugin
1
+ # Stack Internal Backend Plugin
2
2
 
3
- Backend counterpart of the Stack Overflow for Teams Plugin.
3
+ Backend counterpart of the Stack Internal Plugin.
4
4
 
5
5
  ## Areas of Responsibility
6
6
 
7
- The **Stack Overflow for Teams Backend plugin** is responsible for:
7
+ The **Stack Internal Backend plugin** is responsible for:
8
8
 
9
- - **Indexing all questions** from the private Stack Overflow instance (an enhanced version of the existing community plugins in the Backstage repository).
10
- - **Handling API requests** via ``createStackOverflowApi`` and ``createStackOverflowService`` to the Stack Overflow instance for retrieving:
9
+ - **Indexing all questions** from the private Stack Overflow Internal instance (an enhanced version of the existing community plugins in the Backstage repository).
10
+ - **Handling API requests** via ``createStackOverflowApi`` and ``createStackOverflowService`` to the Stack Overflow Internal instance for retrieving:
11
11
  - `/users`
12
12
  - `/tags`
13
13
  - `/questions`
14
14
  - Posting new questions via `/questions`
15
- - **Managing OAuth authentication flow** to securely access Stack Overflow private instances via ``createStackOverflowAuth``
16
- - **HTTP-only cookie** the Stack Overflow Token is set as a secure http-only cookie to the frontend with 24 hours expiration.
15
+ - **Managing OAuth authentication flow** to securely access Stack Overflow Internal private instances via ``createStackOverflowAuth``
16
+ - **HTTP-only cookie** the Stack Overflow Internal Token is set as a secure http-only cookie to the frontend with 24 hours expiration.
17
17
 
18
18
  ## OAuth Authentication Flow
19
19
 
20
- The backend is the only component that directly utilizes the **encrypted Stack Overflow access tokens** for requests.
20
+ The backend is the only component that directly utilizes the **encrypted Stack Overflow Internal access tokens** for requests.
21
21
 
22
22
  ### **Authorization Flow Details**
23
23
 
@@ -31,16 +31,16 @@ The backend is the only component that directly utilizes the **encrypted Stack O
31
31
  #### **`/callback`**
32
32
 
33
33
  - Retrieves the stored **Code Verifier** and **State**.
34
- - Validates that the received **state** matches the one from Stack Overflow's query string parameter.
34
+ - Validates that the received **state** matches the one from Stack Overflow Internal's query string parameter.
35
35
  - The backend requests an **Access Token** using the stored **Code Verifier**.
36
- - Stores the **Stack Overflow Access Token** in a **secure, HTTP-only cookie**.
36
+ - Stores the **Stack Overflow Internal Access Token** in a **secure, HTTP-only cookie**.
37
37
 
38
38
  ## Installation
39
39
 
40
- This plugin is installed via the `backstage-plugin-stack-overflow-teams-backend` package. To install it to your backend package, run the following command:
40
+ This plugin is installed via the `@stackoverflow/backstage-plugin-stack-overflow-teams-backend` package. To install it to your backend package, run the following command:
41
41
 
42
42
  ```bash
43
- yarn --cwd packages/backend add backstage-plugin-stack-overflow-teams-backend
43
+ yarn --cwd packages/backend add @stackoverflow/backstage-plugin-stack-overflow-teams-backend
44
44
  ```
45
45
 
46
46
  Then add the plugin to your backend in `packages/backend/src/index.ts`:
@@ -50,7 +50,7 @@ const backend = createBackend();
50
50
 
51
51
  // ...
52
52
 
53
- backend.add(import('backstage-plugin-stack-overflow-teams-backend'));
53
+ backend.add(import('@stackoverflow/backstage-plugin-stack-overflow-teams-backend'));
54
54
  ```
55
55
 
56
56
  ## Development
package/config.d.ts CHANGED
@@ -16,34 +16,34 @@
16
16
 
17
17
  export interface Config {
18
18
  /**
19
- * Configuration options for the stack overflow for teams frontend plugin.
19
+ * Configuration options for the Stack Internal frontend plugin.
20
20
  *
21
- * This configuration is shared with the backstage-plugin-stack-overflow-teams-backend and backstage-stack-overflow-teams-collator
21
+ * This configuration is shared with the @stackoverflow/backstage-plugin-stack-overflow-teams-backend and @stackoverflow/backstage-stack-overflow-teams-collator
22
22
  */
23
23
  stackoverflow?: {
24
24
  /**
25
- * The base url of the Stack Overflow API used for the plugin
25
+ * The base url of the Stack Overflow Internal API used for the plugin
26
26
  */
27
27
  baseUrl: string;
28
28
 
29
29
  /**
30
- * The API Access Token to authenticate to Stack Overflow API Version 3
30
+ * The API Access Token to authenticate to Stack Overflow Internal API Version 3
31
31
  * @visibility secret
32
32
  */
33
33
  apiAccessToken: string;
34
34
 
35
35
  /**
36
- * The name of the team for a Stack Overflow for Teams account, required for Basic and Business tiers.
36
+ * The name of the team for a Stack Internal account, required for Basic and Business tiers.
37
37
  */
38
38
  teamName?: string;
39
39
 
40
40
  /**
41
- * Client Id for the OAuth Application, required only for Stack Overflow Enterprise and write actions.
41
+ * Client Id for the OAuth Application, required only for Stack Internal Enterprise and write actions.
42
42
  */
43
43
  clientId?: number;
44
44
 
45
45
  /**
46
- * RedirectUri for the OAuth Application, required only for Stack Overflow Enterprise and write actions.
46
+ * RedirectUri for the OAuth Application, required only for Stack Internal Enterprise and write actions.
47
47
  *
48
48
  * This should be your Backstage application domain ending in the plugin's <StackOverflowTeamsPage /> route
49
49
  * If not specified this will got to your <app.baseUrl>/stack-overflow-teams
@@ -1 +1 @@
1
- {"version":3,"file":"createStackOverflowApi.cjs.js","sources":["../../src/api/createStackOverflowApi.ts"],"sourcesContent":["export const createStackOverflowApi = (baseUrl: string) => {\n const request = async <T>(\n endpoint: string,\n method: 'GET' | 'POST',\n authToken: string,\n teamName?: string,\n body?: any,\n searchQuery?: string,\n pageSize?: number,\n page?: number,\n additionalParams?: Record<string, string>\n ): Promise<T> => {\n let url = teamName\n ? `${baseUrl}/v3/teams/${teamName}${endpoint}`\n : `${baseUrl}/api/v3${endpoint}`;\n\n const queryParams = new URLSearchParams();\n\n if (searchQuery) {\n queryParams.append('query', searchQuery);\n }\n\n if (pageSize) {\n queryParams.append('pageSize', pageSize.toString());\n }\n\n if (page) {\n queryParams.append('page', page.toString());\n }\n\n if (additionalParams) {\n Object.entries(additionalParams).forEach(([key, value]) => {\n queryParams.append(key, value);\n });\n }\n\n if (queryParams.toString()) {\n url += `?${queryParams.toString()}`;\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'User-Agent': 'SOBackstage-Plugin',\n Authorization: `Bearer ${authToken}`,\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const responseData = await response.json();\n\n if (!response.ok) {\n const error = new Error(`API Request failed: ${response.statusText}`);\n (error as any).status = response.status;\n (error as any).responseData = responseData;\n throw error;\n }\n return responseData;\n };\n\n return {\n GET: <T>(endpoint: string, authToken: string, teamName?: string, additionalParams?: Record<string, string>) =>\n request<T>(endpoint, 'GET', authToken, teamName, undefined, undefined, undefined, undefined, additionalParams),\n\n POST: <T>(endpoint: string, body: any, authToken: string, teamName?: string) =>\n request<T>(endpoint, 'POST', authToken, teamName, body),\n\n SEARCH: <T>(endpoint: string, searchQuery: string, authToken: string, teamName?: string, page?: number) =>\n request<T>(endpoint, 'GET', authToken, teamName, undefined, searchQuery, 30, page),\n };\n};"],"names":[],"mappings":";;AAAa,MAAA,sBAAA,GAAyB,CAAC,OAAoB,KAAA;AACzD,EAAM,MAAA,OAAA,GAAU,OACd,QAAA,EACA,MACA,EAAA,SAAA,EACA,UACA,IACA,EAAA,WAAA,EACA,QACA,EAAA,IAAA,EACA,gBACe,KAAA;AACf,IAAA,IAAI,GAAM,GAAA,QAAA,GACN,CAAG,EAAA,OAAO,CAAa,UAAA,EAAA,QAAQ,CAAG,EAAA,QAAQ,CAC1C,CAAA,GAAA,CAAA,EAAG,OAAO,CAAA,OAAA,EAAU,QAAQ,CAAA,CAAA;AAEhC,IAAM,MAAA,WAAA,GAAc,IAAI,eAAgB,EAAA;AAExC,IAAA,IAAI,WAAa,EAAA;AACf,MAAY,WAAA,CAAA,MAAA,CAAO,SAAS,WAAW,CAAA;AAAA;AAGzC,IAAA,IAAI,QAAU,EAAA;AACZ,MAAA,WAAA,CAAY,MAAO,CAAA,UAAA,EAAY,QAAS,CAAA,QAAA,EAAU,CAAA;AAAA;AAGpD,IAAA,IAAI,IAAM,EAAA;AACR,MAAA,WAAA,CAAY,MAAO,CAAA,MAAA,EAAQ,IAAK,CAAA,QAAA,EAAU,CAAA;AAAA;AAG5C,IAAA,IAAI,gBAAkB,EAAA;AACpB,MAAO,MAAA,CAAA,OAAA,CAAQ,gBAAgB,CAAE,CAAA,OAAA,CAAQ,CAAC,CAAC,GAAA,EAAK,KAAK,CAAM,KAAA;AACzD,QAAY,WAAA,CAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,OAC9B,CAAA;AAAA;AAGH,IAAI,IAAA,WAAA,CAAY,UAAY,EAAA;AAC1B,MAAO,GAAA,IAAA,CAAA,CAAA,EAAI,WAAY,CAAA,QAAA,EAAU,CAAA,CAAA;AAAA;AAGnC,IAAA,MAAM,OAAkC,GAAA;AAAA,MACtC,cAAgB,EAAA,kBAAA;AAAA,MAChB,YAAc,EAAA,oBAAA;AAAA,MACd,aAAA,EAAe,UAAU,SAAS,CAAA;AAAA,KACpC;AAEA,IAAM,MAAA,QAAA,GAAW,MAAM,KAAA,CAAM,GAAK,EAAA;AAAA,MAChC,MAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAM,EAAA,IAAA,GAAO,IAAK,CAAA,SAAA,CAAU,IAAI,CAAI,GAAA,KAAA;AAAA,KACrC,CAAA;AAED,IAAM,MAAA,YAAA,GAAe,MAAM,QAAA,CAAS,IAAK,EAAA;AAEzC,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAM,QAAQ,IAAI,KAAA,CAAM,CAAuB,oBAAA,EAAA,QAAA,CAAS,UAAU,CAAE,CAAA,CAAA;AACpE,MAAC,KAAA,CAAc,SAAS,QAAS,CAAA,MAAA;AACjC,MAAC,MAAc,YAAe,GAAA,YAAA;AAC9B,MAAM,MAAA,KAAA;AAAA;AAER,IAAO,OAAA,YAAA;AAAA,GACT;AAEA,EAAO,OAAA;AAAA,IACL,GAAK,EAAA,CAAI,QAAkB,EAAA,SAAA,EAAmB,UAAmB,gBAC/D,KAAA,OAAA,CAAW,QAAU,EAAA,KAAA,EAAO,WAAW,QAAU,EAAA,KAAA,CAAA,EAAW,KAAW,CAAA,EAAA,KAAA,CAAA,EAAW,QAAW,gBAAgB,CAAA;AAAA,IAE/G,IAAA,EAAM,CAAI,QAAA,EAAkB,IAAW,EAAA,SAAA,EAAmB,QACxD,KAAA,OAAA,CAAW,QAAU,EAAA,MAAA,EAAQ,SAAW,EAAA,QAAA,EAAU,IAAI,CAAA;AAAA,IAExD,MAAQ,EAAA,CAAI,QAAkB,EAAA,WAAA,EAAqB,WAAmB,QAAmB,EAAA,IAAA,KACvF,OAAW,CAAA,QAAA,EAAU,OAAO,SAAW,EAAA,QAAA,EAAU,KAAW,CAAA,EAAA,WAAA,EAAa,IAAI,IAAI;AAAA,GACrF;AACF;;;;"}
1
+ {"version":3,"file":"createStackOverflowApi.cjs.js","sources":["../../src/api/createStackOverflowApi.ts"],"sourcesContent":["export const createStackOverflowApi = (baseUrl: string) => {\n const request = async <T>(\n endpoint: string,\n method: 'GET' | 'POST',\n authToken: string,\n teamName?: string,\n body?: any,\n searchQuery?: string,\n pageSize?: number,\n page?: number,\n additionalParams?: Record<string, string>\n ): Promise<T> => {\n let url = teamName\n ? `${baseUrl}/v3/teams/${teamName}${endpoint}`\n : `${baseUrl}/api/v3${endpoint}`;\n\n const queryParams = new URLSearchParams();\n\n if (searchQuery) {\n queryParams.append('query', searchQuery);\n }\n\n if (pageSize) {\n queryParams.append('pageSize', pageSize.toString());\n }\n\n if (page) {\n queryParams.append('page', page.toString());\n }\n\n if (additionalParams) {\n Object.entries(additionalParams).forEach(([key, value]) => {\n queryParams.append(key, value);\n });\n }\n\n if (queryParams.toString()) {\n url += `?${queryParams.toString()}`;\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'User-Agent': 'SOBackstage-Plugin',\n Authorization: `Bearer ${authToken}`,\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const responseData = await response.json();\n\n if (!response.ok) {\n const error = new Error(`API Request failed: ${response.statusText}`);\n (error as any).status = response.status;\n (error as any).responseData = responseData;\n throw error;\n }\n return responseData;\n };\n\n return {\n GET: <T>(endpoint: string, authToken: string, teamName?: string, additionalParams?: Record<string, string>) =>\n request<T>(endpoint, 'GET', authToken, teamName, undefined, undefined, undefined, undefined, additionalParams),\n\n POST: <T>(endpoint: string, body: any, authToken: string, teamName?: string) =>\n request<T>(endpoint, 'POST', authToken, teamName, body),\n\n SEARCH: <T>(endpoint: string, searchQuery: string, authToken: string, teamName?: string, page?: number) =>\n request<T>(endpoint, 'GET', authToken, teamName, undefined, searchQuery, 30, page),\n };\n};"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"createStackOverflowAuth.cjs.js","sources":["../../src/api/createStackOverflowAuth.ts"],"sourcesContent":["import { LoggerService } from '@backstage/backend-plugin-api';\nimport { StackOverflowConfig } from '../services/StackOverflowService/types';\nimport crypto from 'crypto';\n\nexport function createStackOverflowAuth(\n config: StackOverflowConfig,\n logger: LoggerService,\n) {\n async function generatePKCECodeVerifier(): Promise<{\n codeVerifier: string;\n codeChallenge: string;\n }> {\n const codeVerifier = crypto.randomBytes(32).toString('hex');\n const hashed = crypto\n .createHash('sha256')\n .update(codeVerifier)\n .digest('base64url');\n return { codeVerifier, codeChallenge: hashed };\n }\n\n async function getAuthUrl(): Promise<{\n url: string;\n codeVerifier: string;\n state: string;\n }> {\n if (!config.clientId || !config.redirectUri) {\n throw new Error(\n 'clientId and redirectUri are required for authentication',\n );\n }\n const { codeVerifier, codeChallenge } = await generatePKCECodeVerifier();\n const state = crypto.randomBytes(16).toString('hex');\n const authUrl = `${config.baseUrl}/oauth?client_id=${\n config.clientId\n }&redirect_uri=${encodeURIComponent(\n config.redirectUri,\n )}&code_challenge=${codeChallenge}&code_challenge_method=S256&state=${state}&scope=write_access`;\n\n return { url: authUrl, codeVerifier, state };\n }\n\n async function exchangeCodeForToken(\n code: string,\n codeVerifier: string,\n ): Promise<{ accessToken: string; expires: number }> {\n if (!config.clientId || !config.redirectUri) {\n throw new Error(\n 'clientId and redirectUri are required for authentication',\n );\n }\n const tokenUrl = `${config.baseUrl}/oauth/access_token/json`;\n const queryParams = new URLSearchParams({\n client_id: String(config.clientId),\n code,\n redirect_uri: config.redirectUri,\n code_verifier: codeVerifier,\n });\n\n const response = await fetch(`${tokenUrl}?${queryParams.toString()}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n });\n\n if (!response.ok) {\n logger.error('Failed to exchange code for access token');\n throw new Error(await response.text());\n }\n const data = await response.json();\n return {\n accessToken: data.access_token,\n expires: data.expires,\n };\n }\n\n return {\n getAuthUrl,\n exchangeCodeForToken,\n config: config,\n };\n}\n"],"names":["crypto"],"mappings":";;;;;;;;AAIgB,SAAA,uBAAA,CACd,QACA,MACA,EAAA;AACA,EAAA,eAAe,wBAGZ,GAAA;AACD,IAAA,MAAM,eAAeA,uBAAO,CAAA,WAAA,CAAY,EAAE,CAAA,CAAE,SAAS,KAAK,CAAA;AAC1D,IAAM,MAAA,MAAA,GAASA,wBACZ,UAAW,CAAA,QAAQ,EACnB,MAAO,CAAA,YAAY,CACnB,CAAA,MAAA,CAAO,WAAW,CAAA;AACrB,IAAO,OAAA,EAAE,YAAc,EAAA,aAAA,EAAe,MAAO,EAAA;AAAA;AAG/C,EAAA,eAAe,UAIZ,GAAA;AACD,IAAA,IAAI,CAAC,MAAA,CAAO,QAAY,IAAA,CAAC,OAAO,WAAa,EAAA;AAC3C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA;AAEF,IAAA,MAAM,EAAE,YAAA,EAAc,aAAc,EAAA,GAAI,MAAM,wBAAyB,EAAA;AACvE,IAAA,MAAM,QAAQA,uBAAO,CAAA,WAAA,CAAY,EAAE,CAAA,CAAE,SAAS,KAAK,CAAA;AACnD,IAAA,MAAM,UAAU,CAAG,EAAA,MAAA,CAAO,OAAO,CAC/B,iBAAA,EAAA,MAAA,CAAO,QACT,CAAiB,cAAA,EAAA,kBAAA;AAAA,MACf,MAAO,CAAA;AAAA,KACR,CAAA,gBAAA,EAAmB,aAAa,CAAA,kCAAA,EAAqC,KAAK,CAAA,mBAAA,CAAA;AAE3E,IAAA,OAAO,EAAE,GAAA,EAAK,OAAS,EAAA,YAAA,EAAc,KAAM,EAAA;AAAA;AAG7C,EAAe,eAAA,oBAAA,CACb,MACA,YACmD,EAAA;AACnD,IAAA,IAAI,CAAC,MAAA,CAAO,QAAY,IAAA,CAAC,OAAO,WAAa,EAAA;AAC3C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA;AAEF,IAAM,MAAA,QAAA,GAAW,CAAG,EAAA,MAAA,CAAO,OAAO,CAAA,wBAAA,CAAA;AAClC,IAAM,MAAA,WAAA,GAAc,IAAI,eAAgB,CAAA;AAAA,MACtC,SAAA,EAAW,MAAO,CAAA,MAAA,CAAO,QAAQ,CAAA;AAAA,MACjC,IAAA;AAAA,MACA,cAAc,MAAO,CAAA,WAAA;AAAA,MACrB,aAAe,EAAA;AAAA,KAChB,CAAA;AAED,IAAM,MAAA,QAAA,GAAW,MAAM,KAAM,CAAA,CAAA,EAAG,QAAQ,CAAI,CAAA,EAAA,WAAA,CAAY,QAAS,EAAC,CAAI,CAAA,EAAA;AAAA,MACpE,MAAQ,EAAA,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,mCAAoC;AAAA,KAChE,CAAA;AAED,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAA,CAAO,MAAM,0CAA0C,CAAA;AACvD,MAAA,MAAM,IAAI,KAAA,CAAM,MAAM,QAAA,CAAS,MAAM,CAAA;AAAA;AAEvC,IAAM,MAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAK,EAAA;AACjC,IAAO,OAAA;AAAA,MACL,aAAa,IAAK,CAAA,YAAA;AAAA,MAClB,SAAS,IAAK,CAAA;AAAA,KAChB;AAAA;AAGF,EAAO,OAAA;AAAA,IACL,UAAA;AAAA,IACA,oBAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
1
+ {"version":3,"file":"createStackOverflowAuth.cjs.js","sources":["../../src/api/createStackOverflowAuth.ts"],"sourcesContent":["import { LoggerService } from '@backstage/backend-plugin-api';\nimport { StackOverflowConfig } from '../services/StackOverflowService/types';\nimport crypto from 'crypto';\n\nexport function createStackOverflowAuth(\n config: StackOverflowConfig,\n logger: LoggerService,\n) {\n async function generatePKCECodeVerifier(): Promise<{\n codeVerifier: string;\n codeChallenge: string;\n }> {\n const codeVerifier = crypto.randomBytes(32).toString('hex');\n const hashed = crypto\n .createHash('sha256')\n .update(codeVerifier)\n .digest('base64url');\n return { codeVerifier, codeChallenge: hashed };\n }\n\n async function getAuthUrl(): Promise<{\n url: string;\n codeVerifier: string;\n state: string;\n }> {\n if (!config.clientId || !config.redirectUri) {\n throw new Error(\n 'clientId and redirectUri are required for authentication',\n );\n }\n const { codeVerifier, codeChallenge } = await generatePKCECodeVerifier();\n const state = crypto.randomBytes(16).toString('hex');\n const authUrl = `${config.baseUrl}/oauth?client_id=${\n config.clientId\n }&redirect_uri=${encodeURIComponent(\n config.redirectUri,\n )}&code_challenge=${codeChallenge}&code_challenge_method=S256&state=${state}&scope=write_access`;\n\n return { url: authUrl, codeVerifier, state };\n }\n\n async function exchangeCodeForToken(\n code: string,\n codeVerifier: string,\n ): Promise<{ accessToken: string; expires: number }> {\n if (!config.clientId || !config.redirectUri) {\n throw new Error(\n 'clientId and redirectUri are required for authentication',\n );\n }\n const tokenUrl = `${config.baseUrl}/oauth/access_token/json`;\n const queryParams = new URLSearchParams({\n client_id: String(config.clientId),\n code,\n redirect_uri: config.redirectUri,\n code_verifier: codeVerifier,\n });\n\n const response = await fetch(`${tokenUrl}?${queryParams.toString()}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n });\n\n if (!response.ok) {\n logger.error('Failed to exchange code for access token');\n throw new Error(await response.text());\n }\n const data = await response.json();\n return {\n accessToken: data.access_token,\n expires: data.expires,\n };\n }\n\n return {\n getAuthUrl,\n exchangeCodeForToken,\n config: config,\n };\n}\n"],"names":["crypto"],"mappings":";;;;;;;;AAIO,SAAS,uBAAA,CACd,QACA,MAAA,EACA;AACA,EAAA,eAAe,wBAAA,GAGZ;AACD,IAAA,MAAM,eAAeA,uBAAA,CAAO,WAAA,CAAY,EAAE,CAAA,CAAE,SAAS,KAAK,CAAA;AAC1D,IAAA,MAAM,MAAA,GAASA,wBACZ,UAAA,CAAW,QAAQ,EACnB,MAAA,CAAO,YAAY,CAAA,CACnB,MAAA,CAAO,WAAW,CAAA;AACrB,IAAA,OAAO,EAAE,YAAA,EAAc,aAAA,EAAe,MAAA,EAAO;AAAA,EAC/C;AAEA,EAAA,eAAe,UAAA,GAIZ;AACD,IAAA,IAAI,CAAC,MAAA,CAAO,QAAA,IAAY,CAAC,OAAO,WAAA,EAAa;AAC3C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,EAAE,YAAA,EAAc,aAAA,EAAc,GAAI,MAAM,wBAAA,EAAyB;AACvE,IAAA,MAAM,QAAQA,uBAAA,CAAO,WAAA,CAAY,EAAE,CAAA,CAAE,SAAS,KAAK,CAAA;AACnD,IAAA,MAAM,UAAU,CAAA,EAAG,MAAA,CAAO,OAAO,CAAA,iBAAA,EAC/B,MAAA,CAAO,QACT,CAAA,cAAA,EAAiB,kBAAA;AAAA,MACf,MAAA,CAAO;AAAA,KACR,CAAA,gBAAA,EAAmB,aAAa,CAAA,kCAAA,EAAqC,KAAK,CAAA,mBAAA,CAAA;AAE3E,IAAA,OAAO,EAAE,GAAA,EAAK,OAAA,EAAS,YAAA,EAAc,KAAA,EAAM;AAAA,EAC7C;AAEA,EAAA,eAAe,oBAAA,CACb,MACA,YAAA,EACmD;AACnD,IAAA,IAAI,CAAC,MAAA,CAAO,QAAA,IAAY,CAAC,OAAO,WAAA,EAAa;AAC3C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,QAAA,GAAW,CAAA,EAAG,MAAA,CAAO,OAAO,CAAA,wBAAA,CAAA;AAClC,IAAA,MAAM,WAAA,GAAc,IAAI,eAAA,CAAgB;AAAA,MACtC,SAAA,EAAW,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAA;AAAA,MACjC,IAAA;AAAA,MACA,cAAc,MAAA,CAAO,WAAA;AAAA,MACrB,aAAA,EAAe;AAAA,KAChB,CAAA;AAED,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,WAAA,CAAY,QAAA,EAAU,CAAA,CAAA,EAAI;AAAA,MACpE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,mCAAA;AAAoC,KAChE,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAA,CAAO,MAAM,0CAA0C,CAAA;AACvD,MAAA,MAAM,IAAI,KAAA,CAAM,MAAM,QAAA,CAAS,MAAM,CAAA;AAAA,IACvC;AACA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO;AAAA,MACL,aAAa,IAAA,CAAK,YAAA;AAAA,MAClB,SAAS,IAAA,CAAK;AAAA,KAChB;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,UAAA;AAAA,IACA,oBAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './router';\nimport { createStackOverflowService } from './services/StackOverflowService';\nimport { StackOverflowConfig } from './services/StackOverflowService';\n\n/**\n * stackOverflowTeamsPlugin backend plugin\n *\n * @public\n */\n\nexport const stackOverflowTeamsPlugin = createBackendPlugin({\n pluginId: 'stack-overflow-teams',\n register(env) {\n env.registerInit({\n deps: {\n logger: coreServices.logger,\n httpRouter: coreServices.httpRouter,\n config: coreServices.rootConfig,\n },\n async init({ logger, httpRouter, config }) {\n const forceOriginUrl = (baseUrl: string): string =>\n `${new URL(baseUrl).origin}`;\n\n const teamName = config.getOptionalString('stackoverflow.teamName');\n\n // If teamName is provided, always use api.stackoverflowteams.com\n const baseUrl = teamName\n ? 'https://api.stackoverflowteams.com'\n : forceOriginUrl(\n config.getOptionalString('stackoverflow.baseUrl') ||\n 'https://api.stackoverflowteams.com',\n );\n\n const stackOverflowConfig: StackOverflowConfig = {\n baseUrl,\n teamName,\n clientId: config.getOptionalNumber('stackoverflow.clientId'),\n redirectUri:\n config.getOptionalString('stackoverflow.redirectUri') ||\n `${config.getString('app.baseUrl')}/stack-overflow-teams`,\n };\n\n const stackOverflowService = await createStackOverflowService({\n config: stackOverflowConfig,\n logger,\n });\n\n httpRouter.use(\n await createRouter({\n stackOverflowConfig,\n logger,\n stackOverflowService,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","baseUrl","createStackOverflowService","createRouter"],"mappings":";;;;;;AAcO,MAAM,2BAA2BA,oCAAoB,CAAA;AAAA,EAC1D,QAAU,EAAA,sBAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,MAAA;AAAA,QACrB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,QAAQA,6BAAa,CAAA;AAAA,OACvB;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,MAAQ,EAAA,UAAA,EAAY,QAAU,EAAA;AACzC,QAAM,MAAA,cAAA,GAAiB,CAACC,QACtB,KAAA,CAAA,EAAG,IAAI,GAAIA,CAAAA,QAAO,EAAE,MAAM,CAAA,CAAA;AAE5B,QAAM,MAAA,QAAA,GAAW,MAAO,CAAA,iBAAA,CAAkB,wBAAwB,CAAA;AAGlE,QAAM,MAAA,OAAA,GAAU,WACZ,oCACA,GAAA,cAAA;AAAA,UACE,MAAA,CAAO,iBAAkB,CAAA,uBAAuB,CAC9C,IAAA;AAAA,SACJ;AAEJ,QAAA,MAAM,mBAA2C,GAAA;AAAA,UAC/C,OAAA;AAAA,UACA,QAAA;AAAA,UACA,QAAA,EAAU,MAAO,CAAA,iBAAA,CAAkB,wBAAwB,CAAA;AAAA,UAC3D,WAAA,EACE,OAAO,iBAAkB,CAAA,2BAA2B,KACpD,CAAG,EAAA,MAAA,CAAO,SAAU,CAAA,aAAa,CAAC,CAAA,qBAAA;AAAA,SACtC;AAEA,QAAM,MAAA,oBAAA,GAAuB,MAAMC,qDAA2B,CAAA;AAAA,UAC5D,MAAQ,EAAA,mBAAA;AAAA,UACR;AAAA,SACD,CAAA;AAED,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,mBAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './router';\nimport { createStackOverflowService } from './services/StackOverflowService';\nimport { StackOverflowConfig } from './services/StackOverflowService';\n\n/**\n * stackOverflowTeamsPlugin backend plugin\n *\n * @public\n */\n\nexport const stackOverflowTeamsPlugin = createBackendPlugin({\n pluginId: 'stack-overflow-teams',\n register(env) {\n env.registerInit({\n deps: {\n logger: coreServices.logger,\n httpRouter: coreServices.httpRouter,\n config: coreServices.rootConfig,\n },\n async init({ logger, httpRouter, config }) {\n const forceOriginUrl = (baseUrl: string): string =>\n `${new URL(baseUrl).origin}`;\n\n const teamName = config.getOptionalString('stackoverflow.teamName');\n\n // If teamName is provided, always use api.stackoverflowteams.com\n const baseUrl = teamName\n ? 'https://api.stackoverflowteams.com'\n : forceOriginUrl(\n config.getOptionalString('stackoverflow.baseUrl') ||\n 'https://api.stackoverflowteams.com',\n );\n\n const stackOverflowConfig: StackOverflowConfig = {\n baseUrl,\n teamName,\n clientId: config.getOptionalNumber('stackoverflow.clientId'),\n redirectUri:\n config.getOptionalString('stackoverflow.redirectUri') ||\n `${config.getString('app.baseUrl')}/stack-overflow-teams`,\n };\n\n const stackOverflowService = await createStackOverflowService({\n config: stackOverflowConfig,\n logger,\n });\n\n httpRouter.use(\n await createRouter({\n stackOverflowConfig,\n logger,\n stackOverflowService,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","baseUrl","createStackOverflowService","createRouter"],"mappings":";;;;;;AAcO,MAAM,2BAA2BA,oCAAA,CAAoB;AAAA,EAC1D,QAAA,EAAU,sBAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,QAAQC,6BAAA,CAAa,MAAA;AAAA,QACrB,YAAYA,6BAAA,CAAa,UAAA;AAAA,QACzB,QAAQA,6BAAA,CAAa;AAAA,OACvB;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,UAAA,EAAY,QAAO,EAAG;AACzC,QAAA,MAAM,cAAA,GAAiB,CAACC,QAAAA,KACtB,CAAA,EAAG,IAAI,GAAA,CAAIA,QAAO,EAAE,MAAM,CAAA,CAAA;AAE5B,QAAA,MAAM,QAAA,GAAW,MAAA,CAAO,iBAAA,CAAkB,wBAAwB,CAAA;AAGlE,QAAA,MAAM,OAAA,GAAU,WACZ,oCAAA,GACA,cAAA;AAAA,UACE,MAAA,CAAO,iBAAA,CAAkB,uBAAuB,CAAA,IAC9C;AAAA,SACJ;AAEJ,QAAA,MAAM,mBAAA,GAA2C;AAAA,UAC/C,OAAA;AAAA,UACA,QAAA;AAAA,UACA,QAAA,EAAU,MAAA,CAAO,iBAAA,CAAkB,wBAAwB,CAAA;AAAA,UAC3D,WAAA,EACE,OAAO,iBAAA,CAAkB,2BAA2B,KACpD,CAAA,EAAG,MAAA,CAAO,SAAA,CAAU,aAAa,CAAC,CAAA,qBAAA;AAAA,SACtC;AAEA,QAAA,MAAM,oBAAA,GAAuB,MAAMC,qDAAA,CAA2B;AAAA,UAC5D,MAAA,EAAQ,mBAAA;AAAA,UACR;AAAA,SACD,CAAA;AAED,QAAA,UAAA,CAAW,GAAA;AAAA,UACT,MAAMC,mBAAA,CAAa;AAAA,YACjB,mBAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA,MACF;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
@@ -38,7 +38,7 @@ async function createRouter({
38
38
  }
39
39
  return authToken;
40
40
  } catch (error) {
41
- logger.error("Invalid or malformed Stack Overflow token", error);
41
+ logger.error("Invalid or malformed Stack Overflow Internal token", error);
42
42
  res.clearCookie("stackoverflow-access-token");
43
43
  return null;
44
44
  }
@@ -110,7 +110,7 @@ async function createRouter({
110
110
  }).json({ ok: true });
111
111
  } catch (error) {
112
112
  logger.error("Failed to exchange code for token", error);
113
- return res.clearCookie("socodeverifier").clearCookie("state").status(500).json({ error: "Failed to authenticate to Stack Overflow for Teams" });
113
+ return res.clearCookie("socodeverifier").clearCookie("state").status(500).json({ error: "Failed to authenticate to Stack Internal" });
114
114
  }
115
115
  });
116
116
  router.get("/authStatus", async (req, res) => {
@@ -118,7 +118,7 @@ async function createRouter({
118
118
  const authToken = getValidAuthToken(req, res);
119
119
  if (!authToken) {
120
120
  res.clearCookie("stackoverflow-access-token");
121
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
121
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
122
122
  }
123
123
  const baseUrl = stackOverflowConfig.baseUrl;
124
124
  const teamName = stackOverflowConfig.teamName;
@@ -135,7 +135,7 @@ async function createRouter({
135
135
  logger.error(`Token validation failed: ${await userResponse.text()}`);
136
136
  return res.status(500).json({ error: "Failed to validate token" });
137
137
  }
138
- return res.status(200).json({ ok: "Stack Overflow Teams Access Token detected" });
138
+ return res.status(200).json({ ok: "Stack Internal Access Token detected" });
139
139
  } catch (error) {
140
140
  logger.error("Error getting authentication status:", error);
141
141
  return res.status(500).json({ error: "Internal Server Error" });
@@ -154,7 +154,7 @@ async function createRouter({
154
154
  headers: { Authorization: `Bearer ${accessToken}` }
155
155
  });
156
156
  if (validationResponse.status === 401 || validationResponse.status === 403) {
157
- return res.status(401).json({ error: "Invalid Stack Overflow token" });
157
+ return res.status(401).json({ error: "Invalid Stack Overflow Internal token" });
158
158
  }
159
159
  if (!validationResponse.ok) {
160
160
  logger.error(
@@ -166,7 +166,7 @@ async function createRouter({
166
166
  httpOnly: true,
167
167
  secure: process.env.NODE_ENV === "production",
168
168
  sameSite: "strict"
169
- }).json({ ok: true, message: "Stack Overflow token accepted" });
169
+ }).json({ ok: true, message: "Stack Overflow Internal token accepted" });
170
170
  } catch (error) {
171
171
  logger.error("Error setting manual access token:", error);
172
172
  return res.status(500).json({ error: "Internal Server Error" });
@@ -190,8 +190,8 @@ async function createRouter({
190
190
  }
191
191
  return res.json({ SOInstance: baseUrl });
192
192
  } catch (error) {
193
- console.error("Error fetching Stack Overflow base URL:", error);
194
- return res.status(500).json({ error: "Failed to fetch Stack Overflow base URL" });
193
+ console.error("Error fetching Stack Overflow Internal base URL:", error);
194
+ return res.status(500).json({ error: "Failed to fetch Stack Overflow Internal base URL" });
195
195
  }
196
196
  });
197
197
  router.get("/me", async (req, res) => {
@@ -202,7 +202,7 @@ async function createRouter({
202
202
  } catch (error) {
203
203
  logger.error("Error fetching questions", { error });
204
204
  res.status(500).send({
205
- error: `Failed to fetch questions from the Stack Overflow instance`
205
+ error: `Failed to fetch questions from the Stack Overflow Internal instance`
206
206
  });
207
207
  }
208
208
  });
@@ -210,7 +210,7 @@ async function createRouter({
210
210
  try {
211
211
  const authToken = getValidAuthToken(req, res);
212
212
  if (!authToken) {
213
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
213
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
214
214
  }
215
215
  const filters = buildQuestionFilters(req.query);
216
216
  const questions = await stackOverflowService.getQuestions(authToken, filters);
@@ -218,7 +218,7 @@ async function createRouter({
218
218
  } catch (error) {
219
219
  logger.error("Error fetching questions", { error });
220
220
  return res.status(500).send({
221
- error: `Failed to fetch questions from the Stack Overflow instance`
221
+ error: `Failed to fetch questions from the Stack Overflow Internal instance`
222
222
  });
223
223
  }
224
224
  });
@@ -226,7 +226,7 @@ async function createRouter({
226
226
  try {
227
227
  const authToken = getValidAuthToken(req, res);
228
228
  if (!authToken) {
229
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
229
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
230
230
  }
231
231
  const page = req.query.page ? parseInt(req.query.page, 10) : void 0;
232
232
  const questions = await stackOverflowService.getActiveQuestions(authToken, page);
@@ -234,7 +234,7 @@ async function createRouter({
234
234
  } catch (error) {
235
235
  logger.error("Error fetching active questions", { error });
236
236
  return res.status(500).send({
237
- error: `Failed to fetch active questions from the Stack Overflow instance`
237
+ error: `Failed to fetch active questions from the Stack Overflow Internal instance`
238
238
  });
239
239
  }
240
240
  });
@@ -242,7 +242,7 @@ async function createRouter({
242
242
  try {
243
243
  const authToken = getValidAuthToken(req, res);
244
244
  if (!authToken) {
245
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
245
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
246
246
  }
247
247
  const page = req.query.page ? parseInt(req.query.page, 10) : void 0;
248
248
  const questions = await stackOverflowService.getNewestQuestions(authToken, page);
@@ -250,7 +250,7 @@ async function createRouter({
250
250
  } catch (error) {
251
251
  logger.error("Error fetching newest questions", { error });
252
252
  return res.status(500).send({
253
- error: `Failed to fetch newest questions from the Stack Overflow instance`
253
+ error: `Failed to fetch newest questions from the Stack Overflow Internal instance`
254
254
  });
255
255
  }
256
256
  });
@@ -258,7 +258,7 @@ async function createRouter({
258
258
  try {
259
259
  const authToken = getValidAuthToken(req, res);
260
260
  if (!authToken) {
261
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
261
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
262
262
  }
263
263
  const page = req.query.page ? parseInt(req.query.page, 10) : void 0;
264
264
  const questions = await stackOverflowService.getTopScoredQuestions(authToken, page);
@@ -266,7 +266,7 @@ async function createRouter({
266
266
  } catch (error) {
267
267
  logger.error("Error fetching top scored questions", { error });
268
268
  return res.status(500).send({
269
- error: `Failed to fetch top scored questions from the Stack Overflow instance`
269
+ error: `Failed to fetch top scored questions from the Stack Overflow Internal instance`
270
270
  });
271
271
  }
272
272
  });
@@ -274,7 +274,7 @@ async function createRouter({
274
274
  try {
275
275
  const authToken = getValidAuthToken(req, res);
276
276
  if (!authToken) {
277
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
277
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
278
278
  }
279
279
  const page = req.query.page ? parseInt(req.query.page, 10) : void 0;
280
280
  const questions = await stackOverflowService.getUnansweredQuestions(authToken, page);
@@ -282,7 +282,7 @@ async function createRouter({
282
282
  } catch (error) {
283
283
  logger.error("Error fetching unanswered questions", { error });
284
284
  return res.status(500).send({
285
- error: `Failed to fetch unanswered questions from the Stack Overflow instance`
285
+ error: `Failed to fetch unanswered questions from the Stack Overflow Internal instance`
286
286
  });
287
287
  }
288
288
  });
@@ -290,7 +290,7 @@ async function createRouter({
290
290
  try {
291
291
  const authToken = getValidAuthToken(req, res);
292
292
  if (!authToken) {
293
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
293
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
294
294
  }
295
295
  const search = req.query.search;
296
296
  const tags = await stackOverflowService.getTags(authToken, search);
@@ -298,7 +298,7 @@ async function createRouter({
298
298
  } catch (error) {
299
299
  logger.error("Error fetching tags", { error });
300
300
  return res.status(500).send({
301
- error: `Failed to fetch tags from the Stack Overflow instance`
301
+ error: `Failed to fetch tags from the Stack Overflow Internal instance`
302
302
  });
303
303
  }
304
304
  });
@@ -306,14 +306,14 @@ async function createRouter({
306
306
  try {
307
307
  const authToken = getValidAuthToken(req, res);
308
308
  if (!authToken) {
309
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
309
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
310
310
  }
311
311
  const users = await stackOverflowService.getUsers(authToken);
312
312
  return res.send(users);
313
313
  } catch (error) {
314
314
  logger.error("Error fetching users", { error });
315
315
  return res.status(500).send({
316
- error: `Failed to fetch users from the Stack Overflow instance`
316
+ error: `Failed to fetch users from the Stack Overflow Internal instance`
317
317
  });
318
318
  }
319
319
  });
@@ -322,7 +322,7 @@ async function createRouter({
322
322
  const authToken = getValidAuthToken(req, res);
323
323
  const { query, page } = req.body;
324
324
  if (!authToken) {
325
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
325
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
326
326
  }
327
327
  const searchResults = await stackOverflowService.getSearch(
328
328
  query,
@@ -333,7 +333,7 @@ async function createRouter({
333
333
  } catch (error) {
334
334
  logger.error("Error searching items", { error });
335
335
  return res.status(500).json({
336
- error: `Failed to search items on the Stack Overflow instance`
336
+ error: `Failed to search items on the Stack Overflow Internal instance`
337
337
  });
338
338
  }
339
339
  });
@@ -342,7 +342,7 @@ async function createRouter({
342
342
  const { title, body, tags } = req.body;
343
343
  const authToken = getValidAuthToken(req, res);
344
344
  if (!authToken) {
345
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
345
+ return res.status(401).json({ error: "Missing Stack Internal Access Token" });
346
346
  }
347
347
  const question = await stackOverflowService.postQuestions(
348
348
  title,
@@ -359,7 +359,7 @@ async function createRouter({
359
359
  });
360
360
  }
361
361
  return res.status(500).json({
362
- error: `Failed to post question to the Stack Overflow instance`
362
+ error: `Failed to post question to the Stack Overflow Internal instance`
363
363
  });
364
364
  }
365
365
  });
@@ -1 +1 @@
1
- {"version":3,"file":"router.cjs.js","sources":["../src/router.ts"],"sourcesContent":["import { LoggerService } from '@backstage/backend-plugin-api';\nimport express, { Request, Response } from 'express';\nimport Router from 'express-promise-router';\nimport {\n StackOverflowAPI,\n StackOverflowConfig,\n QuestionFilters, \n} from './services/StackOverflowService/types';\nimport { createStackOverflowAuth } from './api';\n\nexport async function createRouter({\n logger,\n stackOverflowConfig,\n stackOverflowService,\n}: {\n logger: LoggerService;\n stackOverflowConfig: StackOverflowConfig;\n stackOverflowService: StackOverflowAPI;\n}): Promise<express.Router> {\n const router = Router();\n const authService = createStackOverflowAuth(stackOverflowConfig, logger);\n\n router.use(express.json());\n\n // Parse cookies\n function cookieParse(req: Request): Record<string, string> {\n const rawCookies = req.headers.cookie;\n if (!rawCookies) return {};\n return Object.fromEntries(\n rawCookies.split('; ').map(cookie => {\n const [key, ...value] = cookie.split('=');\n return [key, value.join('=')];\n }),\n );\n }\n\n function getValidAuthToken(req: Request, res: Response): string | null {\n const cookies = cookieParse(req);\n const cookiesToken = cookies['stackoverflow-access-token'];\n\n try {\n const authToken = cookiesToken;\n if (!authToken) {\n res.clearCookie('stackoverflow-access-token');\n return null;\n }\n return authToken;\n } catch (error: any) {\n logger.error('Invalid or malformed Stack Overflow token', error);\n res.clearCookie('stackoverflow-access-token');\n return null;\n }\n }\n\n // Helper function to build question filters from query parameters\n function buildQuestionFilters(query: any): QuestionFilters | undefined {\n const filters: QuestionFilters = {};\n let hasFilters = false;\n\n if (query.sort && ['activity', 'creation', 'score'].includes(query.sort)) {\n filters.sort = query.sort;\n hasFilters = true;\n }\n\n if (query.order && ['asc', 'desc'].includes(query.order)) {\n filters.order = query.order;\n hasFilters = true;\n }\n\n if (query.isAnswered !== undefined) {\n filters.isAnswered = query.isAnswered === 'true';\n hasFilters = true;\n }\n\n if (query.page !== undefined) {\n const page = parseInt(query.page, 10);\n if (!isNaN(page) && page > 0) {\n filters.page = page;\n hasFilters = true;\n }\n }\n\n if (query.pageSize !== undefined) {\n const pageSize = parseInt(query.pageSize, 10);\n if (!isNaN(pageSize) && pageSize > 0) {\n filters.pageSize = pageSize;\n hasFilters = true;\n }\n }\n\n return hasFilters ? filters : undefined;\n }\n\n // OAuth Authentication routes\n\n router.get('/auth/start', async (_req: Request, res: Response) => {\n const { url, codeVerifier, state } = await authService.getAuthUrl();\n\n res.cookie('socodeverifier', codeVerifier, {\n httpOnly: true,\n sameSite: 'strict',\n secure: process.env.NODE_ENV === 'production',\n });\n res.cookie('state', state, {\n httpOnly: true,\n sameSite: 'strict',\n secure: process.env.NODE_ENV === 'production',\n });\n res.json({ authUrl: url });\n });\n\n router.get('/callback', async (req, res) => {\n const cookies = cookieParse(req);\n const storedState = cookies.state;\n const codeVerifier = cookies.socodeverifier;\n const code = req.query.code as string;\n const state = req.query.state as string;\n\n try {\n if (state !== storedState) {\n return res\n .clearCookie('socodeverifier')\n .clearCookie('state')\n .status(401)\n .json({ error: 'Invalid State' });\n }\n\n const { accessToken, expires } = await authService.exchangeCodeForToken(\n code,\n codeVerifier,\n );\n\n // The cookie's max age is linked to the Token's expiration, the default expiration is 24 hours.\n return res\n .clearCookie('socodeverifier')\n .clearCookie('state')\n .cookie('stackoverflow-access-token', accessToken, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: expires * 1000,\n })\n .json({ ok: true });\n } catch (error: any) {\n logger.error('Failed to exchange code for token', error);\n return res\n .clearCookie('socodeverifier')\n .clearCookie('state')\n .status(500)\n .json({ error: 'Failed to authenticate to Stack Overflow for Teams' });\n }\n });\n\n router.get('/authStatus', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n\n if (!authToken) {\n res.clearCookie('stackoverflow-access-token');\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n\n const baseUrl = stackOverflowConfig.baseUrl;\n const teamName = stackOverflowConfig.teamName;\n\n // Use the team-specific API endpoint for basic and business teams\n const userApiUrl = teamName\n ? `${baseUrl}/v3/teams/${teamName}/users/me`\n : `${baseUrl}/api/v3/users/me`;\n\n const userResponse = await fetch(userApiUrl, {\n headers: { Authorization: `Bearer ${authToken}` },\n });\n\n if (userResponse.status === 401 || userResponse.status === 403) {\n res.clearCookie('stackoverflow-access-token');\n return res.status(401).json({ error: 'Invalid or expired token' });\n }\n\n if (!userResponse.ok) {\n res.clearCookie('stackoverflow-access-token');\n logger.error(`Token validation failed: ${await userResponse.text()}`);\n return res.status(500).json({ error: 'Failed to validate token' });\n }\n\n return res\n .status(200)\n .json({ ok: 'Stack Overflow Teams Access Token detected' });\n } catch (error: any) {\n logger.error('Error getting authentication status:', error);\n return res.status(500).json({ error: 'Internal Server Error' });\n }\n });\n\n // PAT Input Route (basic and business support)\n\n router.post('/auth/token', async (req: Request, res: Response) => {\n try {\n const { accessToken } = req.body;\n\n if (!accessToken || typeof accessToken !== 'string') {\n return res\n .status(400)\n .json({ error: 'Valid access token is required' });\n }\n\n const baseUrl = stackOverflowConfig.baseUrl;\n const teamName = stackOverflowConfig.teamName;\n\n // Use the team-specific API endpoint for basic and business teams\n const validationUrl = teamName\n ? `${baseUrl}/v3/teams/${teamName}/users/me`\n : `${baseUrl}/api/v3/users/me`;\n\n const validationResponse = await fetch(validationUrl, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n\n if (\n validationResponse.status === 401 ||\n validationResponse.status === 403\n ) {\n return res.status(401).json({ error: 'Invalid Stack Overflow token' });\n }\n\n if (!validationResponse.ok) {\n logger.error(\n `Token validation failed: ${await validationResponse.text()}`,\n );\n return res.status(500).json({ error: 'Failed to validate token' });\n }\n\n return res\n .cookie('stackoverflow-access-token', accessToken, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n })\n .json({ ok: true, message: 'Stack Overflow token accepted' });\n } catch (error: any) {\n logger.error('Error setting manual access token:', error);\n return res.status(500).json({ error: 'Internal Server Error' });\n }\n });\n\n // Logout route\n\n router.post('/logout', async (_req: Request, res: Response) => {\n try {\n res\n .clearCookie('stackoverflow-access-token')\n .status(200)\n .json({ ok: true });\n } catch (error: any) {\n logger.error('Error removing authentication token:', error);\n res.status(500).json({ error: 'Internal Server Error' });\n }\n });\n\n // Info routes\n\n router.get('/baseurl', async (_req: Request, res: Response) => {\n try {\n const baseUrl = stackOverflowConfig.baseUrl;\n const teamsAPIUrl = 'https://api.stackoverflowteams.com';\n const teamsBaseUrl = `https://stackoverflowteams.com/c/${stackOverflowConfig.teamName}`;\n\n if (baseUrl === teamsAPIUrl) {\n return res.json({ SOInstance: teamsBaseUrl, teamName: stackOverflowConfig.teamName });\n }\n\n return res.json({ SOInstance: baseUrl });\n } catch (error) {\n console.error('Error fetching Stack Overflow base URL:', error);\n return res\n .status(500)\n .json({ error: 'Failed to fetch Stack Overflow base URL' });\n }\n });\n\n // Read routes\n\n router.get('/me', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n const me = await stackOverflowService.getMe(authToken!);\n res.send(me);\n } catch (error: any) {\n // Fix type issue when including the error for some reason\n logger.error('Error fetching questions', { error });\n res.status(500).send({\n error: `Failed to fetch questions from the Stack Overflow instance`,\n });\n }\n });\n\n router.get('/questions', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n \n const filters = buildQuestionFilters(req.query);\n const questions = await stackOverflowService.getQuestions(authToken, filters);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching questions', { error });\n return res.status(500).send({\n error: `Failed to fetch questions from the Stack Overflow instance`,\n });\n }\n });\n\n // Convenience routes for common question filtering scenarios\n router.get('/questions/active', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n \n const page = req.query.page ? parseInt(req.query.page as string, 10) : undefined;\n const questions = await stackOverflowService.getActiveQuestions(authToken, page);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching active questions', { error });\n return res.status(500).send({\n error: `Failed to fetch active questions from the Stack Overflow instance`,\n });\n }\n });\n\n router.get('/questions/newest', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n \n const page = req.query.page ? parseInt(req.query.page as string, 10) : undefined;\n const questions = await stackOverflowService.getNewestQuestions(authToken, page);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching newest questions', { error });\n return res.status(500).send({\n error: `Failed to fetch newest questions from the Stack Overflow instance`,\n });\n }\n });\n\n router.get('/questions/top-scored', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n \n const page = req.query.page ? parseInt(req.query.page as string, 10) : undefined;\n const questions = await stackOverflowService.getTopScoredQuestions(authToken, page);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching top scored questions', { error });\n return res.status(500).send({\n error: `Failed to fetch top scored questions from the Stack Overflow instance`,\n });\n }\n });\n\n router.get('/questions/unanswered', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n \n const page = req.query.page ? parseInt(req.query.page as string, 10) : undefined;\n const questions = await stackOverflowService.getUnansweredQuestions(authToken, page);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching unanswered questions', { error });\n return res.status(500).send({\n error: `Failed to fetch unanswered questions from the Stack Overflow instance`,\n });\n }\n });\n\n router.get('/tags', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n const search = req.query.search as string | undefined;\n const tags = await stackOverflowService.getTags(authToken, search);\n return res.send(tags);\n } catch (error: any) {\n logger.error('Error fetching tags', { error });\n return res.status(500).send({\n error: `Failed to fetch tags from the Stack Overflow instance`,\n });\n }\n });\n\n router.get('/users', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n const users = await stackOverflowService.getUsers(authToken);\n return res.send(users);\n } catch (error: any) {\n logger.error('Error fetching users', { error });\n return res.status(500).send({\n error: `Failed to fetch users from the Stack Overflow instance`,\n });\n }\n });\n\n router.post('/search', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n const { query, page } = req.body;\n\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n const searchResults = await stackOverflowService.getSearch(\n query,\n authToken,\n page\n );\n return res.status(201).json(searchResults);\n } catch (error: any) {\n logger.error('Error searching items', { error });\n return res.status(500).json({\n error: `Failed to search items on the Stack Overflow instance`,\n });\n }\n });\n\n // Write routes\n\n router.post('/questions', async (req: Request, res: Response) => {\n try {\n const { title, body, tags } = req.body;\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Overflow Teams Access Token' });\n }\n const question = await stackOverflowService.postQuestions(\n title,\n body,\n tags,\n authToken,\n );\n return res.status(201).json(question);\n } catch (error: any) {\n if (error.status === 400) {\n return res.status(400).json({\n error: error.responseData?.detail || 'Validation failed',\n validationDetails: error.responseData,\n });\n }\n return res.status(500).json({\n error: `Failed to post question to the Stack Overflow instance`,\n });\n }\n });\n\n return router;\n}"],"names":["Router","createStackOverflowAuth","express"],"mappings":"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"}
1
+ {"version":3,"file":"router.cjs.js","sources":["../src/router.ts"],"sourcesContent":["import { LoggerService } from '@backstage/backend-plugin-api';\nimport express, { Request, Response } from 'express';\nimport Router from 'express-promise-router';\nimport {\n StackOverflowAPI,\n StackOverflowConfig,\n QuestionFilters, \n} from './services/StackOverflowService/types';\nimport { createStackOverflowAuth } from './api';\n\nexport async function createRouter({\n logger,\n stackOverflowConfig,\n stackOverflowService,\n}: {\n logger: LoggerService;\n stackOverflowConfig: StackOverflowConfig;\n stackOverflowService: StackOverflowAPI;\n}): Promise<express.Router> {\n const router = Router();\n const authService = createStackOverflowAuth(stackOverflowConfig, logger);\n\n router.use(express.json());\n\n // Parse cookies\n function cookieParse(req: Request): Record<string, string> {\n const rawCookies = req.headers.cookie;\n if (!rawCookies) return {};\n return Object.fromEntries(\n rawCookies.split('; ').map(cookie => {\n const [key, ...value] = cookie.split('=');\n return [key, value.join('=')];\n }),\n );\n }\n\n function getValidAuthToken(req: Request, res: Response): string | null {\n const cookies = cookieParse(req);\n const cookiesToken = cookies['stackoverflow-access-token'];\n\n try {\n const authToken = cookiesToken;\n if (!authToken) {\n res.clearCookie('stackoverflow-access-token');\n return null;\n }\n return authToken;\n } catch (error: any) {\n logger.error('Invalid or malformed Stack Overflow Internal token', error);\n res.clearCookie('stackoverflow-access-token');\n return null;\n }\n }\n\n // Helper function to build question filters from query parameters\n function buildQuestionFilters(query: any): QuestionFilters | undefined {\n const filters: QuestionFilters = {};\n let hasFilters = false;\n\n if (query.sort && ['activity', 'creation', 'score'].includes(query.sort)) {\n filters.sort = query.sort;\n hasFilters = true;\n }\n\n if (query.order && ['asc', 'desc'].includes(query.order)) {\n filters.order = query.order;\n hasFilters = true;\n }\n\n if (query.isAnswered !== undefined) {\n filters.isAnswered = query.isAnswered === 'true';\n hasFilters = true;\n }\n\n if (query.page !== undefined) {\n const page = parseInt(query.page, 10);\n if (!isNaN(page) && page > 0) {\n filters.page = page;\n hasFilters = true;\n }\n }\n\n if (query.pageSize !== undefined) {\n const pageSize = parseInt(query.pageSize, 10);\n if (!isNaN(pageSize) && pageSize > 0) {\n filters.pageSize = pageSize;\n hasFilters = true;\n }\n }\n\n return hasFilters ? filters : undefined;\n }\n\n // OAuth Authentication routes\n\n router.get('/auth/start', async (_req: Request, res: Response) => {\n const { url, codeVerifier, state } = await authService.getAuthUrl();\n\n res.cookie('socodeverifier', codeVerifier, {\n httpOnly: true,\n sameSite: 'strict',\n secure: process.env.NODE_ENV === 'production',\n });\n res.cookie('state', state, {\n httpOnly: true,\n sameSite: 'strict',\n secure: process.env.NODE_ENV === 'production',\n });\n res.json({ authUrl: url });\n });\n\n router.get('/callback', async (req, res) => {\n const cookies = cookieParse(req);\n const storedState = cookies.state;\n const codeVerifier = cookies.socodeverifier;\n const code = req.query.code as string;\n const state = req.query.state as string;\n\n try {\n if (state !== storedState) {\n return res\n .clearCookie('socodeverifier')\n .clearCookie('state')\n .status(401)\n .json({ error: 'Invalid State' });\n }\n\n const { accessToken, expires } = await authService.exchangeCodeForToken(\n code,\n codeVerifier,\n );\n\n // The cookie's max age is linked to the Token's expiration, the default expiration is 24 hours.\n return res\n .clearCookie('socodeverifier')\n .clearCookie('state')\n .cookie('stackoverflow-access-token', accessToken, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: expires * 1000,\n })\n .json({ ok: true });\n } catch (error: any) {\n logger.error('Failed to exchange code for token', error);\n return res\n .clearCookie('socodeverifier')\n .clearCookie('state')\n .status(500)\n .json({ error: 'Failed to authenticate to Stack Internal' });\n }\n });\n\n router.get('/authStatus', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n\n if (!authToken) {\n res.clearCookie('stackoverflow-access-token');\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n\n const baseUrl = stackOverflowConfig.baseUrl;\n const teamName = stackOverflowConfig.teamName;\n\n // Use the team-specific API endpoint for basic and business teams\n const userApiUrl = teamName\n ? `${baseUrl}/v3/teams/${teamName}/users/me`\n : `${baseUrl}/api/v3/users/me`;\n\n const userResponse = await fetch(userApiUrl, {\n headers: { Authorization: `Bearer ${authToken}` },\n });\n\n if (userResponse.status === 401 || userResponse.status === 403) {\n res.clearCookie('stackoverflow-access-token');\n return res.status(401).json({ error: 'Invalid or expired token' });\n }\n\n if (!userResponse.ok) {\n res.clearCookie('stackoverflow-access-token');\n logger.error(`Token validation failed: ${await userResponse.text()}`);\n return res.status(500).json({ error: 'Failed to validate token' });\n }\n\n return res\n .status(200)\n .json({ ok: 'Stack Internal Access Token detected' });\n } catch (error: any) {\n logger.error('Error getting authentication status:', error);\n return res.status(500).json({ error: 'Internal Server Error' });\n }\n });\n\n // PAT Input Route (basic and business support)\n\n router.post('/auth/token', async (req: Request, res: Response) => {\n try {\n const { accessToken } = req.body;\n\n if (!accessToken || typeof accessToken !== 'string') {\n return res\n .status(400)\n .json({ error: 'Valid access token is required' });\n }\n\n const baseUrl = stackOverflowConfig.baseUrl;\n const teamName = stackOverflowConfig.teamName;\n\n // Use the team-specific API endpoint for basic and business teams\n const validationUrl = teamName\n ? `${baseUrl}/v3/teams/${teamName}/users/me`\n : `${baseUrl}/api/v3/users/me`;\n\n const validationResponse = await fetch(validationUrl, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n\n if (\n validationResponse.status === 401 ||\n validationResponse.status === 403\n ) {\n return res.status(401).json({ error: 'Invalid Stack Overflow Internal token' });\n }\n\n if (!validationResponse.ok) {\n logger.error(\n `Token validation failed: ${await validationResponse.text()}`,\n );\n return res.status(500).json({ error: 'Failed to validate token' });\n }\n\n return res\n .cookie('stackoverflow-access-token', accessToken, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n })\n .json({ ok: true, message: 'Stack Overflow Internal token accepted' });\n } catch (error: any) {\n logger.error('Error setting manual access token:', error);\n return res.status(500).json({ error: 'Internal Server Error' });\n }\n });\n\n // Logout route\n\n router.post('/logout', async (_req: Request, res: Response) => {\n try {\n res\n .clearCookie('stackoverflow-access-token')\n .status(200)\n .json({ ok: true });\n } catch (error: any) {\n logger.error('Error removing authentication token:', error);\n res.status(500).json({ error: 'Internal Server Error' });\n }\n });\n\n // Info routes\n\n router.get('/baseurl', async (_req: Request, res: Response) => {\n try {\n const baseUrl = stackOverflowConfig.baseUrl;\n const teamsAPIUrl = 'https://api.stackoverflowteams.com';\n const teamsBaseUrl = `https://stackoverflowteams.com/c/${stackOverflowConfig.teamName}`;\n\n if (baseUrl === teamsAPIUrl) {\n return res.json({ SOInstance: teamsBaseUrl, teamName: stackOverflowConfig.teamName });\n }\n\n return res.json({ SOInstance: baseUrl });\n } catch (error) {\n console.error('Error fetching Stack Overflow Internal base URL:', error);\n return res\n .status(500)\n .json({ error: 'Failed to fetch Stack Overflow Internal base URL' });\n }\n });\n\n // Read routes\n\n router.get('/me', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n const me = await stackOverflowService.getMe(authToken!);\n res.send(me);\n } catch (error: any) {\n // Fix type issue when including the error for some reason\n logger.error('Error fetching questions', { error });\n res.status(500).send({\n error: `Failed to fetch questions from the Stack Overflow Internal instance`,\n });\n }\n });\n\n router.get('/questions', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n \n const filters = buildQuestionFilters(req.query);\n const questions = await stackOverflowService.getQuestions(authToken, filters);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching questions', { error });\n return res.status(500).send({\n error: `Failed to fetch questions from the Stack Overflow Internal instance`,\n });\n }\n });\n\n // Convenience routes for common question filtering scenarios\n router.get('/questions/active', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n \n const page = req.query.page ? parseInt(req.query.page as string, 10) : undefined;\n const questions = await stackOverflowService.getActiveQuestions(authToken, page);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching active questions', { error });\n return res.status(500).send({\n error: `Failed to fetch active questions from the Stack Overflow Internal instance`,\n });\n }\n });\n\n router.get('/questions/newest', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n \n const page = req.query.page ? parseInt(req.query.page as string, 10) : undefined;\n const questions = await stackOverflowService.getNewestQuestions(authToken, page);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching newest questions', { error });\n return res.status(500).send({\n error: `Failed to fetch newest questions from the Stack Overflow Internal instance`,\n });\n }\n });\n\n router.get('/questions/top-scored', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n \n const page = req.query.page ? parseInt(req.query.page as string, 10) : undefined;\n const questions = await stackOverflowService.getTopScoredQuestions(authToken, page);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching top scored questions', { error });\n return res.status(500).send({\n error: `Failed to fetch top scored questions from the Stack Overflow Internal instance`,\n });\n }\n });\n\n router.get('/questions/unanswered', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n \n const page = req.query.page ? parseInt(req.query.page as string, 10) : undefined;\n const questions = await stackOverflowService.getUnansweredQuestions(authToken, page);\n return res.send(questions);\n } catch (error: any) {\n logger.error('Error fetching unanswered questions', { error });\n return res.status(500).send({\n error: `Failed to fetch unanswered questions from the Stack Overflow Internal instance`,\n });\n }\n });\n\n router.get('/tags', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n const search = req.query.search as string | undefined;\n const tags = await stackOverflowService.getTags(authToken, search);\n return res.send(tags);\n } catch (error: any) {\n logger.error('Error fetching tags', { error });\n return res.status(500).send({\n error: `Failed to fetch tags from the Stack Overflow Internal instance`,\n });\n }\n });\n\n router.get('/users', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n const users = await stackOverflowService.getUsers(authToken);\n return res.send(users);\n } catch (error: any) {\n logger.error('Error fetching users', { error });\n return res.status(500).send({\n error: `Failed to fetch users from the Stack Overflow Internal instance`,\n });\n }\n });\n\n router.post('/search', async (req: Request, res: Response) => {\n try {\n const authToken = getValidAuthToken(req, res);\n const { query, page } = req.body;\n\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n const searchResults = await stackOverflowService.getSearch(\n query,\n authToken,\n page\n );\n return res.status(201).json(searchResults);\n } catch (error: any) {\n logger.error('Error searching items', { error });\n return res.status(500).json({\n error: `Failed to search items on the Stack Overflow Internal instance`,\n });\n }\n });\n\n // Write routes\n\n router.post('/questions', async (req: Request, res: Response) => {\n try {\n const { title, body, tags } = req.body;\n const authToken = getValidAuthToken(req, res);\n if (!authToken) {\n return res\n .status(401)\n .json({ error: 'Missing Stack Internal Access Token' });\n }\n const question = await stackOverflowService.postQuestions(\n title,\n body,\n tags,\n authToken,\n );\n return res.status(201).json(question);\n } catch (error: any) {\n if (error.status === 400) {\n return res.status(400).json({\n error: error.responseData?.detail || 'Validation failed',\n validationDetails: error.responseData,\n });\n }\n return res.status(500).json({\n error: `Failed to post question to the Stack Overflow Internal instance`,\n });\n }\n });\n\n return router;\n}"],"names":["Router","createStackOverflowAuth","express"],"mappings":"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"}
@@ -7,7 +7,7 @@ async function createStackOverflowService({
7
7
  config,
8
8
  logger
9
9
  }) {
10
- logger.info("Initializing Stack Overflow Service");
10
+ logger.info("Initializing Stack Overflow Internal Service");
11
11
  if (config.baseUrl && config.teamName) {
12
12
  logger.warn(
13
13
  "Please note that this integration is not compatible with Enterprise Private Teams. When stackoverflow.teamName is provided the baseUrl will always change to 'api.stackoverflowteams.com'"
@@ -1 +1 @@
1
- {"version":3,"file":"createStackOverflowService.cjs.js","sources":["../../../src/services/StackOverflowService/createStackOverflowService.ts"],"sourcesContent":["import { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n PaginatedResponse,\n Question,\n SearchItem,\n StackOverflowAPI,\n StackOverflowConfig,\n QuestionFilters,\n Tag,\n User,\n} from './types';\nimport { createStackOverflowApi } from '../../api';\n\nexport async function createStackOverflowService({\n config,\n logger,\n}: {\n config: StackOverflowConfig;\n logger: LoggerService;\n}): Promise<StackOverflowAPI> {\n // LOGGER\n logger.info('Initializing Stack Overflow Service');\n\n if (config.baseUrl && config.teamName) {\n logger.warn(\n \"Please note that this integration is not compatible with Enterprise Private Teams. When stackoverflow.teamName is provided the baseUrl will always change to 'api.stackoverflowteams.com'\",\n );\n }\n\n const { baseUrl, teamName } = config;\n const api = createStackOverflowApi(\n baseUrl || 'https://api.stackoverflowteams.com',\n );\n\n // Helper function to build query parameters\n const buildParams = (filters: Record<string, any>): Record<string, string> => {\n const params: Record<string, string> = {};\n \n Object.entries(filters).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n params[key] = value.toString();\n }\n });\n \n return params;\n };\n\n return {\n getQuestions: (authToken: string, filters?: QuestionFilters) => {\n const params = buildParams({\n sort: filters?.sort || 'creation',\n order: filters?.order || 'desc',\n isAnswered: filters?.isAnswered,\n page: filters?.page,\n pageSize: filters?.pageSize || 30,\n });\n \n return api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, params);\n },\n getTags: (authToken: string, search?: string) => {\n const params: Record<string, string> = { sort: 'postCount', order: 'desc' };\n if (search) {\n params.partialName = search;\n }\n return api.GET<PaginatedResponse<Tag>>('/tags', authToken, teamName, params);\n },\n getUsers: authToken =>\n api.GET<PaginatedResponse<User>>('/users', authToken, teamName),\n getMe: authToken => api.GET<User>('/users/me', authToken, teamName),\n \n // Convenience methods for common filtering scenarios\n getActiveQuestions: (authToken: string, page?: number) =>\n api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, \n buildParams({ sort: 'activity', order: 'desc', page })),\n \n getNewestQuestions: (authToken: string, page?: number) =>\n api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, \n buildParams({ sort: 'creation', order: 'desc', page })),\n \n getTopScoredQuestions: (authToken: string, page?: number) =>\n api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, \n buildParams({ sort: 'score', order: 'desc', page })),\n \n getUnansweredQuestions: (authToken: string, page?: number) =>\n api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, \n buildParams({ isAnswered: false, sort: 'creation', order: 'desc', page })),\n \n // POST\n postQuestions: (\n title: string,\n body: string,\n tags: string[],\n authToken: string,\n ) =>\n api.POST<Question>(\n '/questions',\n { title, body, tags },\n authToken,\n teamName,\n ),\n // SEARCH\n getSearch: (query: string, authToken: string, page?: number) =>\n api.SEARCH<PaginatedResponse<SearchItem>>(\n '/search',\n query,\n authToken,\n teamName,\n page,\n ),\n };\n}"],"names":["createStackOverflowApi"],"mappings":"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"}
1
+ {"version":3,"file":"createStackOverflowService.cjs.js","sources":["../../../src/services/StackOverflowService/createStackOverflowService.ts"],"sourcesContent":["import { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n PaginatedResponse,\n Question,\n SearchItem,\n StackOverflowAPI,\n StackOverflowConfig,\n QuestionFilters,\n Tag,\n User,\n} from './types';\nimport { createStackOverflowApi } from '../../api';\n\nexport async function createStackOverflowService({\n config,\n logger,\n}: {\n config: StackOverflowConfig;\n logger: LoggerService;\n}): Promise<StackOverflowAPI> {\n // LOGGER\n logger.info('Initializing Stack Overflow Internal Service');\n\n if (config.baseUrl && config.teamName) {\n logger.warn(\n \"Please note that this integration is not compatible with Enterprise Private Teams. When stackoverflow.teamName is provided the baseUrl will always change to 'api.stackoverflowteams.com'\",\n );\n }\n\n const { baseUrl, teamName } = config;\n const api = createStackOverflowApi(\n baseUrl || 'https://api.stackoverflowteams.com',\n );\n\n // Helper function to build query parameters\n const buildParams = (filters: Record<string, any>): Record<string, string> => {\n const params: Record<string, string> = {};\n \n Object.entries(filters).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n params[key] = value.toString();\n }\n });\n \n return params;\n };\n\n return {\n getQuestions: (authToken: string, filters?: QuestionFilters) => {\n const params = buildParams({\n sort: filters?.sort || 'creation',\n order: filters?.order || 'desc',\n isAnswered: filters?.isAnswered,\n page: filters?.page,\n pageSize: filters?.pageSize || 30,\n });\n \n return api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, params);\n },\n getTags: (authToken: string, search?: string) => {\n const params: Record<string, string> = { sort: 'postCount', order: 'desc' };\n if (search) {\n params.partialName = search;\n }\n return api.GET<PaginatedResponse<Tag>>('/tags', authToken, teamName, params);\n },\n getUsers: authToken =>\n api.GET<PaginatedResponse<User>>('/users', authToken, teamName),\n getMe: authToken => api.GET<User>('/users/me', authToken, teamName),\n \n // Convenience methods for common filtering scenarios\n getActiveQuestions: (authToken: string, page?: number) =>\n api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, \n buildParams({ sort: 'activity', order: 'desc', page })),\n \n getNewestQuestions: (authToken: string, page?: number) =>\n api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, \n buildParams({ sort: 'creation', order: 'desc', page })),\n \n getTopScoredQuestions: (authToken: string, page?: number) =>\n api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, \n buildParams({ sort: 'score', order: 'desc', page })),\n \n getUnansweredQuestions: (authToken: string, page?: number) =>\n api.GET<PaginatedResponse<Question>>('/questions', authToken, teamName, \n buildParams({ isAnswered: false, sort: 'creation', order: 'desc', page })),\n \n // POST\n postQuestions: (\n title: string,\n body: string,\n tags: string[],\n authToken: string,\n ) =>\n api.POST<Question>(\n '/questions',\n { title, body, tags },\n authToken,\n teamName,\n ),\n // SEARCH\n getSearch: (query: string, authToken: string, page?: number) =>\n api.SEARCH<PaginatedResponse<SearchItem>>(\n '/search',\n query,\n authToken,\n teamName,\n page,\n ),\n };\n}"],"names":["createStackOverflowApi"],"mappings":";;;;;AAaA,eAAsB,0BAAA,CAA2B;AAAA,EAC/C,MAAA;AAAA,EACA;AACF,CAAA,EAG8B;AAE5B,EAAA,MAAA,CAAO,KAAK,8CAA8C,CAAA;AAE1D,EAAA,IAAI,MAAA,CAAO,OAAA,IAAW,MAAA,CAAO,QAAA,EAAU;AACrC,IAAA,MAAA,CAAO,IAAA;AAAA,MACL;AAAA,KACF;AAAA,EACF;AAEA,EAAA,MAAM,EAAE,OAAA,EAAS,QAAA,EAAS,GAAI,MAAA;AAC9B,EAAA,MAAM,GAAA,GAAMA,6CAAA;AAAA,IACV,OAAA,IAAW;AAAA,GACb;AAGA,EAAA,MAAM,WAAA,GAAc,CAAC,OAAA,KAAyD;AAC5E,IAAA,MAAM,SAAiC,EAAC;AAExC,IAAA,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM;AAChD,MAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACzC,QAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA,CAAM,QAAA,EAAS;AAAA,MAC/B;AAAA,IACF,CAAC,CAAA;AAED,IAAA,OAAO,MAAA;AAAA,EACT,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,YAAA,EAAc,CAAC,SAAA,EAAmB,OAAA,KAA8B;AAC9D,MAAA,MAAM,SAAS,WAAA,CAAY;AAAA,QACzB,IAAA,EAAM,SAAS,IAAA,IAAQ,UAAA;AAAA,QACvB,KAAA,EAAO,SAAS,KAAA,IAAS,MAAA;AAAA,QACzB,YAAY,OAAA,EAAS,UAAA;AAAA,QACrB,MAAM,OAAA,EAAS,IAAA;AAAA,QACf,QAAA,EAAU,SAAS,QAAA,IAAY;AAAA,OAChC,CAAA;AAED,MAAA,OAAO,GAAA,CAAI,GAAA,CAAiC,YAAA,EAAc,SAAA,EAAW,UAAU,MAAM,CAAA;AAAA,IACvF,CAAA;AAAA,IACA,OAAA,EAAS,CAAC,SAAA,EAAmB,MAAA,KAAoB;AAC/C,MAAA,MAAM,MAAA,GAAiC,EAAE,IAAA,EAAM,WAAA,EAAa,OAAO,MAAA,EAAO;AAC1E,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,MAAA,CAAO,WAAA,GAAc,MAAA;AAAA,MACvB;AACA,MAAA,OAAO,GAAA,CAAI,GAAA,CAA4B,OAAA,EAAS,SAAA,EAAW,UAAU,MAAM,CAAA;AAAA,IAC7E,CAAA;AAAA,IACA,UAAU,CAAA,SAAA,KACR,GAAA,CAAI,GAAA,CAA6B,QAAA,EAAU,WAAW,QAAQ,CAAA;AAAA,IAChE,OAAO,CAAA,SAAA,KAAa,GAAA,CAAI,GAAA,CAAU,WAAA,EAAa,WAAW,QAAQ,CAAA;AAAA;AAAA,IAGlE,kBAAA,EAAoB,CAAC,SAAA,EAAmB,IAAA,KACtC,GAAA,CAAI,GAAA;AAAA,MAAiC,YAAA;AAAA,MAAc,SAAA;AAAA,MAAW,QAAA;AAAA,MAC5D,YAAY,EAAE,IAAA,EAAM,YAAY,KAAA,EAAO,MAAA,EAAQ,MAAM;AAAA,KAAC;AAAA,IAE1D,kBAAA,EAAoB,CAAC,SAAA,EAAmB,IAAA,KACtC,GAAA,CAAI,GAAA;AAAA,MAAiC,YAAA;AAAA,MAAc,SAAA;AAAA,MAAW,QAAA;AAAA,MAC5D,YAAY,EAAE,IAAA,EAAM,YAAY,KAAA,EAAO,MAAA,EAAQ,MAAM;AAAA,KAAC;AAAA,IAE1D,qBAAA,EAAuB,CAAC,SAAA,EAAmB,IAAA,KACzC,GAAA,CAAI,GAAA;AAAA,MAAiC,YAAA;AAAA,MAAc,SAAA;AAAA,MAAW,QAAA;AAAA,MAC5D,YAAY,EAAE,IAAA,EAAM,SAAS,KAAA,EAAO,MAAA,EAAQ,MAAM;AAAA,KAAC;AAAA,IAEvD,sBAAA,EAAwB,CAAC,SAAA,EAAmB,IAAA,KAC1C,GAAA,CAAI,GAAA;AAAA,MAAiC,YAAA;AAAA,MAAc,SAAA;AAAA,MAAW,QAAA;AAAA,MAC5D,WAAA,CAAY,EAAE,UAAA,EAAY,KAAA,EAAO,MAAM,UAAA,EAAY,KAAA,EAAO,MAAA,EAAQ,IAAA,EAAM;AAAA,KAAC;AAAA;AAAA,IAG7E,eAAe,CACb,KAAA,EACA,IAAA,EACA,IAAA,EACA,cAEA,GAAA,CAAI,IAAA;AAAA,MACF,YAAA;AAAA,MACA,EAAE,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK;AAAA,MACpB,SAAA;AAAA,MACA;AAAA,KACF;AAAA;AAAA,IAEF,SAAA,EAAW,CAAC,KAAA,EAAe,SAAA,EAAmB,SAC5C,GAAA,CAAI,MAAA;AAAA,MACF,SAAA;AAAA,MACA,KAAA;AAAA,MACA,SAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA;AACF,GACJ;AACF;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackoverflow/backstage-plugin-stack-overflow-teams-backend",
3
- "version": "1.5.0",
3
+ "version": "1.6.3",
4
4
  "main": "./dist/index.cjs.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -11,8 +11,8 @@
11
11
  "role": "backend-plugin",
12
12
  "pluginId": "stack-overflow-teams",
13
13
  "pluginPackages": [
14
- "backstage-plugin-stack-overflow-teams",
15
- "backstage-plugin-stack-overflow-teams-backend"
14
+ "@stackoverflow/backstage-plugin-stack-overflow-teams",
15
+ "@stackoverflow/backstage-plugin-stack-overflow-teams-backend"
16
16
  ]
17
17
  },
18
18
  "repository": {