diff --git a/.changeset/friendly-sheep-flash.md b/.changeset/friendly-sheep-flash.md new file mode 100644 index 0000000000..58ae309bf1 --- /dev/null +++ b/.changeset/friendly-sheep-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-stack-overflow-backend': patch +--- + +Added API key as separate configuration diff --git a/plugins/stack-overflow-backend/README.md b/plugins/stack-overflow-backend/README.md index f2eaf660c2..62115b889c 100644 --- a/plugins/stack-overflow-backend/README.md +++ b/plugins/stack-overflow-backend/README.md @@ -15,6 +15,16 @@ stackoverflow: baseUrl: https://api.stackexchange.com/2.2 # alternative: your internal stack overflow instance ``` +### Stack Overflow for Teams (private stack overflow instance) + +If you have a private stack overflow instance you will need to supply an API key as well. You can read more about how to set this up by going to [Stack Overflows Help Page](https://stackoverflow.help/en/articles/4385859-stack-overflow-for-teams-api) + +```yaml +stackoverflow: + baseUrl: https://api.stackexchange.com/2.2 # alternative: your internal stack overflow instance + apiKey: $STACK_OVERFLOW_API_KEY +``` + ## Areas of Responsibility This stack overflow backend plugin is primarily responsible for the following: diff --git a/plugins/stack-overflow-backend/api-report.md b/plugins/stack-overflow-backend/api-report.md index fc4c610d3e..1a21ff34ac 100644 --- a/plugins/stack-overflow-backend/api-report.md +++ b/plugins/stack-overflow-backend/api-report.md @@ -41,6 +41,7 @@ export class StackOverflowQuestionsCollatorFactory // @public export type StackOverflowQuestionsCollatorFactoryOptions = { baseUrl?: string; + apiKey?: string; requestParams: StackOverflowQuestionsRequestParams; logger: Logger; }; diff --git a/plugins/stack-overflow-backend/config.d.ts b/plugins/stack-overflow-backend/config.d.ts index 2c39d61bfb..d329a4389f 100644 --- a/plugins/stack-overflow-backend/config.d.ts +++ b/plugins/stack-overflow-backend/config.d.ts @@ -24,5 +24,11 @@ export interface Config { * @visibility backend */ baseUrl?: string; + + /** + * The api key to authenticate to Stack Overflow API + * @visibility secret + */ + apiKey?: string; }; } diff --git a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts index 98fc86c2ea..45261ce546 100644 --- a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts +++ b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts @@ -50,6 +50,7 @@ export type StackOverflowQuestionsRequestParams = { */ export type StackOverflowQuestionsCollatorFactoryOptions = { baseUrl?: string; + apiKey?: string; requestParams: StackOverflowQuestionsRequestParams; logger: Logger; }; @@ -64,11 +65,13 @@ export class StackOverflowQuestionsCollatorFactory { protected requestParams: StackOverflowQuestionsRequestParams; private readonly baseUrl: string | undefined; + private readonly apiKey: string | undefined; private readonly logger: Logger; public readonly type: string = 'stack-overflow'; private constructor(options: StackOverflowQuestionsCollatorFactoryOptions) { this.baseUrl = options.baseUrl; + this.apiKey = options.apiKey; this.requestParams = options.requestParams; this.logger = options.logger; } @@ -77,10 +80,15 @@ export class StackOverflowQuestionsCollatorFactory config: Config, options: StackOverflowQuestionsCollatorFactoryOptions, ) { + const apiKey = config.getOptionalString('stackoverflow.apiKey'); const baseUrl = config.getOptionalString('stackoverflow.baseUrl') || 'https://api.stackexchange.com/2.2'; - return new StackOverflowQuestionsCollatorFactory({ ...options, baseUrl }); + return new StackOverflowQuestionsCollatorFactory({ + ...options, + baseUrl, + apiKey, + }); } async getCollator() { @@ -93,12 +101,28 @@ export class StackOverflowQuestionsCollatorFactory `No stackoverflow.baseUrl configured in your app-config.yaml`, ); } + + try { + if (Object.keys(this.requestParams).indexOf('key') >= 0) { + this.logger.warn( + 'The API Key should be passed as a seperate param to bypass encoding', + ); + delete this.requestParams.key; + } + } catch (e) { + this.logger.error(`Caught ${e}`); + } + const params = qs.stringify(this.requestParams, { arrayFormat: 'comma', addQueryPrefix: true, }); - const res = await fetch(`${this.baseUrl}/questions${params}`); + const apiKeyParam = this.apiKey + ? `${params ? '&' : '?'}key=${this.apiKey}` + : ''; + + const res = await fetch(`${this.baseUrl}/questions${params}${apiKeyParam}`); const data = await res.json(); for (const question of data.items) {