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

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
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@stackoverflow/backstage-plugin-stack-overflow-teams-backend",
3
- "version": "1.5.0",
4
- "main": "./dist/index.cjs.js",
5
- "types": "./dist/index.d.ts",
3
+ "version": "1.5.1",
4
+ "main": "src/index.ts",
5
+ "types": "src/index.ts",
6
6
  "license": "Apache-2.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
@@ -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": {
@@ -60,12 +60,5 @@
60
60
  "files": [
61
61
  "dist",
62
62
  "config.d.ts"
63
- ],
64
- "typesVersions": {
65
- "*": {
66
- "index": [
67
- "dist/index.d.ts"
68
- ]
69
- }
70
- }
63
+ ]
71
64
  }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { stackOverflowTeamsPlugin as default } from './plugin';
@@ -1,51 +0,0 @@
1
- 'use strict';
2
-
3
- const createStackOverflowApi = (baseUrl) => {
4
- const request = async (endpoint, method, authToken, teamName, body, searchQuery, pageSize, page, additionalParams) => {
5
- let url = teamName ? `${baseUrl}/v3/teams/${teamName}${endpoint}` : `${baseUrl}/api/v3${endpoint}`;
6
- const queryParams = new URLSearchParams();
7
- if (searchQuery) {
8
- queryParams.append("query", searchQuery);
9
- }
10
- if (pageSize) {
11
- queryParams.append("pageSize", pageSize.toString());
12
- }
13
- if (page) {
14
- queryParams.append("page", page.toString());
15
- }
16
- if (additionalParams) {
17
- Object.entries(additionalParams).forEach(([key, value]) => {
18
- queryParams.append(key, value);
19
- });
20
- }
21
- if (queryParams.toString()) {
22
- url += `?${queryParams.toString()}`;
23
- }
24
- const headers = {
25
- "Content-Type": "application/json",
26
- "User-Agent": "SOBackstage-Plugin",
27
- Authorization: `Bearer ${authToken}`
28
- };
29
- const response = await fetch(url, {
30
- method,
31
- headers,
32
- body: body ? JSON.stringify(body) : void 0
33
- });
34
- const responseData = await response.json();
35
- if (!response.ok) {
36
- const error = new Error(`API Request failed: ${response.statusText}`);
37
- error.status = response.status;
38
- error.responseData = responseData;
39
- throw error;
40
- }
41
- return responseData;
42
- };
43
- return {
44
- GET: (endpoint, authToken, teamName, additionalParams) => request(endpoint, "GET", authToken, teamName, void 0, void 0, void 0, void 0, additionalParams),
45
- POST: (endpoint, body, authToken, teamName) => request(endpoint, "POST", authToken, teamName, body),
46
- SEARCH: (endpoint, searchQuery, authToken, teamName, page) => request(endpoint, "GET", authToken, teamName, void 0, searchQuery, 30, page)
47
- };
48
- };
49
-
50
- exports.createStackOverflowApi = createStackOverflowApi;
51
- //# sourceMappingURL=createStackOverflowApi.cjs.js.map
@@ -1 +0,0 @@
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,63 +0,0 @@
1
- 'use strict';
2
-
3
- var crypto = require('crypto');
4
-
5
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
6
-
7
- var crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
8
-
9
- function createStackOverflowAuth(config, logger) {
10
- async function generatePKCECodeVerifier() {
11
- const codeVerifier = crypto__default.default.randomBytes(32).toString("hex");
12
- const hashed = crypto__default.default.createHash("sha256").update(codeVerifier).digest("base64url");
13
- return { codeVerifier, codeChallenge: hashed };
14
- }
15
- async function getAuthUrl() {
16
- if (!config.clientId || !config.redirectUri) {
17
- throw new Error(
18
- "clientId and redirectUri are required for authentication"
19
- );
20
- }
21
- const { codeVerifier, codeChallenge } = await generatePKCECodeVerifier();
22
- const state = crypto__default.default.randomBytes(16).toString("hex");
23
- const authUrl = `${config.baseUrl}/oauth?client_id=${config.clientId}&redirect_uri=${encodeURIComponent(
24
- config.redirectUri
25
- )}&code_challenge=${codeChallenge}&code_challenge_method=S256&state=${state}&scope=write_access`;
26
- return { url: authUrl, codeVerifier, state };
27
- }
28
- async function exchangeCodeForToken(code, codeVerifier) {
29
- if (!config.clientId || !config.redirectUri) {
30
- throw new Error(
31
- "clientId and redirectUri are required for authentication"
32
- );
33
- }
34
- const tokenUrl = `${config.baseUrl}/oauth/access_token/json`;
35
- const queryParams = new URLSearchParams({
36
- client_id: String(config.clientId),
37
- code,
38
- redirect_uri: config.redirectUri,
39
- code_verifier: codeVerifier
40
- });
41
- const response = await fetch(`${tokenUrl}?${queryParams.toString()}`, {
42
- method: "POST",
43
- headers: { "Content-Type": "application/x-www-form-urlencoded" }
44
- });
45
- if (!response.ok) {
46
- logger.error("Failed to exchange code for access token");
47
- throw new Error(await response.text());
48
- }
49
- const data = await response.json();
50
- return {
51
- accessToken: data.access_token,
52
- expires: data.expires
53
- };
54
- }
55
- return {
56
- getAuthUrl,
57
- exchangeCodeForToken,
58
- config
59
- };
60
- }
61
-
62
- exports.createStackOverflowAuth = createStackOverflowAuth;
63
- //# sourceMappingURL=createStackOverflowAuth.cjs.js.map
@@ -1 +0,0 @@
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;;;;"}
package/dist/index.cjs.js DELETED
@@ -1,10 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var plugin = require('./plugin.cjs.js');
6
-
7
-
8
-
9
- exports.default = plugin.stackOverflowTeamsPlugin;
10
- //# sourceMappingURL=index.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
package/dist/index.d.ts DELETED
@@ -1,10 +0,0 @@
1
- import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
-
3
- /**
4
- * stackOverflowTeamsPlugin backend plugin
5
- *
6
- * @public
7
- */
8
- declare const stackOverflowTeamsPlugin: _backstage_backend_plugin_api.BackendFeature;
9
-
10
- export { stackOverflowTeamsPlugin as default };
@@ -1,45 +0,0 @@
1
- 'use strict';
2
-
3
- var backendPluginApi = require('@backstage/backend-plugin-api');
4
- var router = require('./router.cjs.js');
5
- var createStackOverflowService = require('./services/StackOverflowService/createStackOverflowService.cjs.js');
6
-
7
- const stackOverflowTeamsPlugin = backendPluginApi.createBackendPlugin({
8
- pluginId: "stack-overflow-teams",
9
- register(env) {
10
- env.registerInit({
11
- deps: {
12
- logger: backendPluginApi.coreServices.logger,
13
- httpRouter: backendPluginApi.coreServices.httpRouter,
14
- config: backendPluginApi.coreServices.rootConfig
15
- },
16
- async init({ logger, httpRouter, config }) {
17
- const forceOriginUrl = (baseUrl2) => `${new URL(baseUrl2).origin}`;
18
- const teamName = config.getOptionalString("stackoverflow.teamName");
19
- const baseUrl = teamName ? "https://api.stackoverflowteams.com" : forceOriginUrl(
20
- config.getOptionalString("stackoverflow.baseUrl") || "https://api.stackoverflowteams.com"
21
- );
22
- const stackOverflowConfig = {
23
- baseUrl,
24
- teamName,
25
- clientId: config.getOptionalNumber("stackoverflow.clientId"),
26
- redirectUri: config.getOptionalString("stackoverflow.redirectUri") || `${config.getString("app.baseUrl")}/stack-overflow-teams`
27
- };
28
- const stackOverflowService = await createStackOverflowService.createStackOverflowService({
29
- config: stackOverflowConfig,
30
- logger
31
- });
32
- httpRouter.use(
33
- await router.createRouter({
34
- stackOverflowConfig,
35
- logger,
36
- stackOverflowService
37
- })
38
- );
39
- }
40
- });
41
- }
42
- });
43
-
44
- exports.stackOverflowTeamsPlugin = stackOverflowTeamsPlugin;
45
- //# sourceMappingURL=plugin.cjs.js.map
@@ -1 +0,0 @@
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,370 +0,0 @@
1
- 'use strict';
2
-
3
- var express = require('express');
4
- var Router = require('express-promise-router');
5
- var createStackOverflowAuth = require('./api/createStackOverflowAuth.cjs.js');
6
-
7
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
-
9
- var express__default = /*#__PURE__*/_interopDefaultCompat(express);
10
- var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
11
-
12
- async function createRouter({
13
- logger,
14
- stackOverflowConfig,
15
- stackOverflowService
16
- }) {
17
- const router = Router__default.default();
18
- const authService = createStackOverflowAuth.createStackOverflowAuth(stackOverflowConfig, logger);
19
- router.use(express__default.default.json());
20
- function cookieParse(req) {
21
- const rawCookies = req.headers.cookie;
22
- if (!rawCookies) return {};
23
- return Object.fromEntries(
24
- rawCookies.split("; ").map((cookie) => {
25
- const [key, ...value] = cookie.split("=");
26
- return [key, value.join("=")];
27
- })
28
- );
29
- }
30
- function getValidAuthToken(req, res) {
31
- const cookies = cookieParse(req);
32
- const cookiesToken = cookies["stackoverflow-access-token"];
33
- try {
34
- const authToken = cookiesToken;
35
- if (!authToken) {
36
- res.clearCookie("stackoverflow-access-token");
37
- return null;
38
- }
39
- return authToken;
40
- } catch (error) {
41
- logger.error("Invalid or malformed Stack Overflow token", error);
42
- res.clearCookie("stackoverflow-access-token");
43
- return null;
44
- }
45
- }
46
- function buildQuestionFilters(query) {
47
- const filters = {};
48
- let hasFilters = false;
49
- if (query.sort && ["activity", "creation", "score"].includes(query.sort)) {
50
- filters.sort = query.sort;
51
- hasFilters = true;
52
- }
53
- if (query.order && ["asc", "desc"].includes(query.order)) {
54
- filters.order = query.order;
55
- hasFilters = true;
56
- }
57
- if (query.isAnswered !== void 0) {
58
- filters.isAnswered = query.isAnswered === "true";
59
- hasFilters = true;
60
- }
61
- if (query.page !== void 0) {
62
- const page = parseInt(query.page, 10);
63
- if (!isNaN(page) && page > 0) {
64
- filters.page = page;
65
- hasFilters = true;
66
- }
67
- }
68
- if (query.pageSize !== void 0) {
69
- const pageSize = parseInt(query.pageSize, 10);
70
- if (!isNaN(pageSize) && pageSize > 0) {
71
- filters.pageSize = pageSize;
72
- hasFilters = true;
73
- }
74
- }
75
- return hasFilters ? filters : void 0;
76
- }
77
- router.get("/auth/start", async (_req, res) => {
78
- const { url, codeVerifier, state } = await authService.getAuthUrl();
79
- res.cookie("socodeverifier", codeVerifier, {
80
- httpOnly: true,
81
- sameSite: "strict",
82
- secure: process.env.NODE_ENV === "production"
83
- });
84
- res.cookie("state", state, {
85
- httpOnly: true,
86
- sameSite: "strict",
87
- secure: process.env.NODE_ENV === "production"
88
- });
89
- res.json({ authUrl: url });
90
- });
91
- router.get("/callback", async (req, res) => {
92
- const cookies = cookieParse(req);
93
- const storedState = cookies.state;
94
- const codeVerifier = cookies.socodeverifier;
95
- const code = req.query.code;
96
- const state = req.query.state;
97
- try {
98
- if (state !== storedState) {
99
- return res.clearCookie("socodeverifier").clearCookie("state").status(401).json({ error: "Invalid State" });
100
- }
101
- const { accessToken, expires } = await authService.exchangeCodeForToken(
102
- code,
103
- codeVerifier
104
- );
105
- return res.clearCookie("socodeverifier").clearCookie("state").cookie("stackoverflow-access-token", accessToken, {
106
- httpOnly: true,
107
- secure: process.env.NODE_ENV === "production",
108
- sameSite: "strict",
109
- maxAge: expires * 1e3
110
- }).json({ ok: true });
111
- } catch (error) {
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" });
114
- }
115
- });
116
- router.get("/authStatus", async (req, res) => {
117
- try {
118
- const authToken = getValidAuthToken(req, res);
119
- if (!authToken) {
120
- res.clearCookie("stackoverflow-access-token");
121
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
122
- }
123
- const baseUrl = stackOverflowConfig.baseUrl;
124
- const teamName = stackOverflowConfig.teamName;
125
- const userApiUrl = teamName ? `${baseUrl}/v3/teams/${teamName}/users/me` : `${baseUrl}/api/v3/users/me`;
126
- const userResponse = await fetch(userApiUrl, {
127
- headers: { Authorization: `Bearer ${authToken}` }
128
- });
129
- if (userResponse.status === 401 || userResponse.status === 403) {
130
- res.clearCookie("stackoverflow-access-token");
131
- return res.status(401).json({ error: "Invalid or expired token" });
132
- }
133
- if (!userResponse.ok) {
134
- res.clearCookie("stackoverflow-access-token");
135
- logger.error(`Token validation failed: ${await userResponse.text()}`);
136
- return res.status(500).json({ error: "Failed to validate token" });
137
- }
138
- return res.status(200).json({ ok: "Stack Overflow Teams Access Token detected" });
139
- } catch (error) {
140
- logger.error("Error getting authentication status:", error);
141
- return res.status(500).json({ error: "Internal Server Error" });
142
- }
143
- });
144
- router.post("/auth/token", async (req, res) => {
145
- try {
146
- const { accessToken } = req.body;
147
- if (!accessToken || typeof accessToken !== "string") {
148
- return res.status(400).json({ error: "Valid access token is required" });
149
- }
150
- const baseUrl = stackOverflowConfig.baseUrl;
151
- const teamName = stackOverflowConfig.teamName;
152
- const validationUrl = teamName ? `${baseUrl}/v3/teams/${teamName}/users/me` : `${baseUrl}/api/v3/users/me`;
153
- const validationResponse = await fetch(validationUrl, {
154
- headers: { Authorization: `Bearer ${accessToken}` }
155
- });
156
- if (validationResponse.status === 401 || validationResponse.status === 403) {
157
- return res.status(401).json({ error: "Invalid Stack Overflow token" });
158
- }
159
- if (!validationResponse.ok) {
160
- logger.error(
161
- `Token validation failed: ${await validationResponse.text()}`
162
- );
163
- return res.status(500).json({ error: "Failed to validate token" });
164
- }
165
- return res.cookie("stackoverflow-access-token", accessToken, {
166
- httpOnly: true,
167
- secure: process.env.NODE_ENV === "production",
168
- sameSite: "strict"
169
- }).json({ ok: true, message: "Stack Overflow token accepted" });
170
- } catch (error) {
171
- logger.error("Error setting manual access token:", error);
172
- return res.status(500).json({ error: "Internal Server Error" });
173
- }
174
- });
175
- router.post("/logout", async (_req, res) => {
176
- try {
177
- res.clearCookie("stackoverflow-access-token").status(200).json({ ok: true });
178
- } catch (error) {
179
- logger.error("Error removing authentication token:", error);
180
- res.status(500).json({ error: "Internal Server Error" });
181
- }
182
- });
183
- router.get("/baseurl", async (_req, res) => {
184
- try {
185
- const baseUrl = stackOverflowConfig.baseUrl;
186
- const teamsAPIUrl = "https://api.stackoverflowteams.com";
187
- const teamsBaseUrl = `https://stackoverflowteams.com/c/${stackOverflowConfig.teamName}`;
188
- if (baseUrl === teamsAPIUrl) {
189
- return res.json({ SOInstance: teamsBaseUrl, teamName: stackOverflowConfig.teamName });
190
- }
191
- return res.json({ SOInstance: baseUrl });
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" });
195
- }
196
- });
197
- router.get("/me", async (req, res) => {
198
- try {
199
- const authToken = getValidAuthToken(req, res);
200
- const me = await stackOverflowService.getMe(authToken);
201
- res.send(me);
202
- } catch (error) {
203
- logger.error("Error fetching questions", { error });
204
- res.status(500).send({
205
- error: `Failed to fetch questions from the Stack Overflow instance`
206
- });
207
- }
208
- });
209
- router.get("/questions", async (req, res) => {
210
- try {
211
- const authToken = getValidAuthToken(req, res);
212
- if (!authToken) {
213
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
214
- }
215
- const filters = buildQuestionFilters(req.query);
216
- const questions = await stackOverflowService.getQuestions(authToken, filters);
217
- return res.send(questions);
218
- } catch (error) {
219
- logger.error("Error fetching questions", { error });
220
- return res.status(500).send({
221
- error: `Failed to fetch questions from the Stack Overflow instance`
222
- });
223
- }
224
- });
225
- router.get("/questions/active", async (req, res) => {
226
- try {
227
- const authToken = getValidAuthToken(req, res);
228
- if (!authToken) {
229
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
230
- }
231
- const page = req.query.page ? parseInt(req.query.page, 10) : void 0;
232
- const questions = await stackOverflowService.getActiveQuestions(authToken, page);
233
- return res.send(questions);
234
- } catch (error) {
235
- logger.error("Error fetching active questions", { error });
236
- return res.status(500).send({
237
- error: `Failed to fetch active questions from the Stack Overflow instance`
238
- });
239
- }
240
- });
241
- router.get("/questions/newest", async (req, res) => {
242
- try {
243
- const authToken = getValidAuthToken(req, res);
244
- if (!authToken) {
245
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
246
- }
247
- const page = req.query.page ? parseInt(req.query.page, 10) : void 0;
248
- const questions = await stackOverflowService.getNewestQuestions(authToken, page);
249
- return res.send(questions);
250
- } catch (error) {
251
- logger.error("Error fetching newest questions", { error });
252
- return res.status(500).send({
253
- error: `Failed to fetch newest questions from the Stack Overflow instance`
254
- });
255
- }
256
- });
257
- router.get("/questions/top-scored", async (req, res) => {
258
- try {
259
- const authToken = getValidAuthToken(req, res);
260
- if (!authToken) {
261
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
262
- }
263
- const page = req.query.page ? parseInt(req.query.page, 10) : void 0;
264
- const questions = await stackOverflowService.getTopScoredQuestions(authToken, page);
265
- return res.send(questions);
266
- } catch (error) {
267
- logger.error("Error fetching top scored questions", { error });
268
- return res.status(500).send({
269
- error: `Failed to fetch top scored questions from the Stack Overflow instance`
270
- });
271
- }
272
- });
273
- router.get("/questions/unanswered", async (req, res) => {
274
- try {
275
- const authToken = getValidAuthToken(req, res);
276
- if (!authToken) {
277
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
278
- }
279
- const page = req.query.page ? parseInt(req.query.page, 10) : void 0;
280
- const questions = await stackOverflowService.getUnansweredQuestions(authToken, page);
281
- return res.send(questions);
282
- } catch (error) {
283
- logger.error("Error fetching unanswered questions", { error });
284
- return res.status(500).send({
285
- error: `Failed to fetch unanswered questions from the Stack Overflow instance`
286
- });
287
- }
288
- });
289
- router.get("/tags", async (req, res) => {
290
- try {
291
- const authToken = getValidAuthToken(req, res);
292
- if (!authToken) {
293
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
294
- }
295
- const search = req.query.search;
296
- const tags = await stackOverflowService.getTags(authToken, search);
297
- return res.send(tags);
298
- } catch (error) {
299
- logger.error("Error fetching tags", { error });
300
- return res.status(500).send({
301
- error: `Failed to fetch tags from the Stack Overflow instance`
302
- });
303
- }
304
- });
305
- router.get("/users", async (req, res) => {
306
- try {
307
- const authToken = getValidAuthToken(req, res);
308
- if (!authToken) {
309
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
310
- }
311
- const users = await stackOverflowService.getUsers(authToken);
312
- return res.send(users);
313
- } catch (error) {
314
- logger.error("Error fetching users", { error });
315
- return res.status(500).send({
316
- error: `Failed to fetch users from the Stack Overflow instance`
317
- });
318
- }
319
- });
320
- router.post("/search", async (req, res) => {
321
- try {
322
- const authToken = getValidAuthToken(req, res);
323
- const { query, page } = req.body;
324
- if (!authToken) {
325
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
326
- }
327
- const searchResults = await stackOverflowService.getSearch(
328
- query,
329
- authToken,
330
- page
331
- );
332
- return res.status(201).json(searchResults);
333
- } catch (error) {
334
- logger.error("Error searching items", { error });
335
- return res.status(500).json({
336
- error: `Failed to search items on the Stack Overflow instance`
337
- });
338
- }
339
- });
340
- router.post("/questions", async (req, res) => {
341
- try {
342
- const { title, body, tags } = req.body;
343
- const authToken = getValidAuthToken(req, res);
344
- if (!authToken) {
345
- return res.status(401).json({ error: "Missing Stack Overflow Teams Access Token" });
346
- }
347
- const question = await stackOverflowService.postQuestions(
348
- title,
349
- body,
350
- tags,
351
- authToken
352
- );
353
- return res.status(201).json(question);
354
- } catch (error) {
355
- if (error.status === 400) {
356
- return res.status(400).json({
357
- error: error.responseData?.detail || "Validation failed",
358
- validationDetails: error.responseData
359
- });
360
- }
361
- return res.status(500).json({
362
- error: `Failed to post question to the Stack Overflow instance`
363
- });
364
- }
365
- });
366
- return router;
367
- }
368
-
369
- exports.createRouter = createRouter;
370
- //# sourceMappingURL=router.cjs.js.map
@@ -1 +0,0 @@
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,93 +0,0 @@
1
- 'use strict';
2
-
3
- var createStackOverflowApi = require('../../api/createStackOverflowApi.cjs.js');
4
- require('crypto');
5
-
6
- async function createStackOverflowService({
7
- config,
8
- logger
9
- }) {
10
- logger.info("Initializing Stack Overflow Service");
11
- if (config.baseUrl && config.teamName) {
12
- logger.warn(
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'"
14
- );
15
- }
16
- const { baseUrl, teamName } = config;
17
- const api = createStackOverflowApi.createStackOverflowApi(
18
- baseUrl || "https://api.stackoverflowteams.com"
19
- );
20
- const buildParams = (filters) => {
21
- const params = {};
22
- Object.entries(filters).forEach(([key, value]) => {
23
- if (value !== void 0 && value !== null) {
24
- params[key] = value.toString();
25
- }
26
- });
27
- return params;
28
- };
29
- return {
30
- getQuestions: (authToken, filters) => {
31
- const params = buildParams({
32
- sort: filters?.sort || "creation",
33
- order: filters?.order || "desc",
34
- isAnswered: filters?.isAnswered,
35
- page: filters?.page,
36
- pageSize: filters?.pageSize || 30
37
- });
38
- return api.GET("/questions", authToken, teamName, params);
39
- },
40
- getTags: (authToken, search) => {
41
- const params = { sort: "postCount", order: "desc" };
42
- if (search) {
43
- params.partialName = search;
44
- }
45
- return api.GET("/tags", authToken, teamName, params);
46
- },
47
- getUsers: (authToken) => api.GET("/users", authToken, teamName),
48
- getMe: (authToken) => api.GET("/users/me", authToken, teamName),
49
- // Convenience methods for common filtering scenarios
50
- getActiveQuestions: (authToken, page) => api.GET(
51
- "/questions",
52
- authToken,
53
- teamName,
54
- buildParams({ sort: "activity", order: "desc", page })
55
- ),
56
- getNewestQuestions: (authToken, page) => api.GET(
57
- "/questions",
58
- authToken,
59
- teamName,
60
- buildParams({ sort: "creation", order: "desc", page })
61
- ),
62
- getTopScoredQuestions: (authToken, page) => api.GET(
63
- "/questions",
64
- authToken,
65
- teamName,
66
- buildParams({ sort: "score", order: "desc", page })
67
- ),
68
- getUnansweredQuestions: (authToken, page) => api.GET(
69
- "/questions",
70
- authToken,
71
- teamName,
72
- buildParams({ isAnswered: false, sort: "creation", order: "desc", page })
73
- ),
74
- // POST
75
- postQuestions: (title, body, tags, authToken) => api.POST(
76
- "/questions",
77
- { title, body, tags },
78
- authToken,
79
- teamName
80
- ),
81
- // SEARCH
82
- getSearch: (query, authToken, page) => api.SEARCH(
83
- "/search",
84
- query,
85
- authToken,
86
- teamName,
87
- page
88
- )
89
- };
90
- }
91
-
92
- exports.createStackOverflowService = createStackOverflowService;
93
- //# sourceMappingURL=createStackOverflowService.cjs.js.map
@@ -1 +0,0 @@
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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"}