@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 +13 -13
- package/config.d.ts +7 -7
- package/dist/api/createStackOverflowApi.cjs.js.map +1 -1
- package/dist/api/createStackOverflowAuth.cjs.js.map +1 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/router.cjs.js +27 -27
- package/dist/router.cjs.js.map +1 -1
- package/dist/services/StackOverflowService/createStackOverflowService.cjs.js +1 -1
- package/dist/services/StackOverflowService/createStackOverflowService.cjs.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
# Stack
|
|
1
|
+
# Stack Internal Backend Plugin
|
|
2
2
|
|
|
3
|
-
Backend counterpart of the Stack
|
|
3
|
+
Backend counterpart of the Stack Internal Plugin.
|
|
4
4
|
|
|
5
5
|
## Areas of Responsibility
|
|
6
6
|
|
|
7
|
-
The **Stack
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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":";;
|
|
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":";;;;;;;;
|
|
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;;;;"}
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -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,
|
|
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;;;;"}
|
package/dist/router.cjs.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
});
|
package/dist/router.cjs.js.map
CHANGED
|
@@ -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":";;;;;AAaA,eAAsB,
|
|
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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackoverflow/backstage-plugin-stack-overflow-teams-backend",
|
|
3
|
-
"version": "1.
|
|
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": {
|