mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-28 17:20:55 -05:00
feat: add internal api url
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
db2d8614a6
commit
f43c9fd2b1
6 changed files with 77 additions and 36 deletions
|
@ -6,6 +6,7 @@
|
||||||
| `HD_BACKEND_PORT` | 3000 | | The port the backend process listens on. |
|
| `HD_BACKEND_PORT` | 3000 | | The port the backend process listens on. |
|
||||||
| `HD_FRONTEND_PORT` | 3001 | | The port the frontend process listens on. |
|
| `HD_FRONTEND_PORT` | 3001 | | The port the frontend process listens on. |
|
||||||
| `HD_RENDERER_BASE_URL` | Content of HD_BASE_URL | | The URL the renderer runs on. If omitted this will be the same as `HD_BASE_URL`. For more detail see [this faq entry][faq-entry] |
|
| `HD_RENDERER_BASE_URL` | Content of HD_BASE_URL | | The URL the renderer runs on. If omitted this will be the same as `HD_BASE_URL`. For more detail see [this faq entry][faq-entry] |
|
||||||
|
| `HD_INTERNAL_API_URL` | Content of HD_BASE_URL | `http://localhost:3000` | This URL is used by the frontend to access the backend directly if it can't reach the backend using the `HD_BASE_URL` |
|
||||||
| `HD_LOGLEVEL` | warn | | The loglevel that should be used. Options are `error`, `warn`, `info`, `debug` or `trace`. |
|
| `HD_LOGLEVEL` | warn | | The loglevel that should be used. Options are `error`, `warn`, `info`, `debug` or `trace`. |
|
||||||
| `HD_SHOW_LOG_TIMESTAMP` | true | | Specifies if a timestamp should be added to the log statements. Disabling is useful for extern log management (systemd etc.) |
|
| `HD_SHOW_LOG_TIMESTAMP` | true | | Specifies if a timestamp should be added to the log statements. Disabling is useful for extern log management (systemd etc.) |
|
||||||
| `HD_FORBIDDEN_NOTE_IDS` | - | `notAllowed,alsoNotAllowed` | A list of note ids (separated by `,`), that are not allowed to be created or requested by anyone. |
|
| `HD_FORBIDDEN_NOTE_IDS` | - | `notAllowed,alsoNotAllowed` | A list of note ids (separated by `,`), that are not allowed to be created or requested by anyone. |
|
||||||
|
|
|
@ -14,7 +14,7 @@ import type { Alias, NewAliasDto, PrimaryAliasDto } from './types'
|
||||||
* @param noteIdOrAlias The note id or an existing alias for a note.
|
* @param noteIdOrAlias The note id or an existing alias for a note.
|
||||||
* @param newAlias The new alias.
|
* @param newAlias The new alias.
|
||||||
* @return Information about the newly created alias.
|
* @return Information about the newly created alias.
|
||||||
* @throws {Error} when the api request wasn't successfull
|
* @throws {Error} when the api request wasn't successful
|
||||||
*/
|
*/
|
||||||
export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise<Alias> => {
|
export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise<Alias> => {
|
||||||
const response = await new PostApiRequestBuilder<Alias, NewAliasDto>('alias')
|
const response = await new PostApiRequestBuilder<Alias, NewAliasDto>('alias')
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type { ApiErrorResponse } from '../api-error-response'
|
||||||
import { ApiResponse } from '../api-response'
|
import { ApiResponse } from '../api-response'
|
||||||
import { defaultConfig, defaultHeaders } from '../default-config'
|
import { defaultConfig, defaultHeaders } from '../default-config'
|
||||||
import deepmerge from 'deepmerge'
|
import deepmerge from 'deepmerge'
|
||||||
|
import { baseUrlFromEnvExtractor } from '../../../utils/base-url-from-env-extractor'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder to construct and execute a call to the HTTP API.
|
* Builder to construct and execute a call to the HTTP API.
|
||||||
|
@ -27,7 +28,18 @@ export abstract class ApiRequestBuilder<ResponseType> {
|
||||||
* @param baseUrl An optional base URL that is used for the endpoint
|
* @param baseUrl An optional base URL that is used for the endpoint
|
||||||
*/
|
*/
|
||||||
constructor(endpoint: string, baseUrl?: string) {
|
constructor(endpoint: string, baseUrl?: string) {
|
||||||
this.targetUrl = `${baseUrl ?? '/'}api/private/${endpoint}`
|
const actualBaseUrl = this.determineBaseUrl(baseUrl)
|
||||||
|
|
||||||
|
this.targetUrl = `${actualBaseUrl}api/private/${endpoint}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the API base URL by checking if the request is made on the server or client
|
||||||
|
*
|
||||||
|
* @return the base url
|
||||||
|
*/
|
||||||
|
private determineBaseUrl(baseUrl?: string) {
|
||||||
|
return typeof window !== 'undefined' ? baseUrl ?? '/' : baseUrlFromEnvExtractor.extractBaseUrls().internalApiUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async sendRequestAndVerifyResponse(httpMethod: RequestInit['method']): Promise<ApiResponse<ResponseType>> {
|
protected async sendRequestAndVerifyResponse(httpMethod: RequestInit['method']): Promise<ApiResponse<ResponseType>> {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import React, { createContext } from 'react'
|
||||||
export interface BaseUrls {
|
export interface BaseUrls {
|
||||||
renderer: string
|
renderer: string
|
||||||
editor: string
|
editor: string
|
||||||
|
internalApiUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BaseUrlContextProviderProps {
|
interface BaseUrlContextProviderProps {
|
||||||
|
|
|
@ -6,71 +6,93 @@
|
||||||
import { BaseUrlFromEnvExtractor } from './base-url-from-env-extractor'
|
import { BaseUrlFromEnvExtractor } from './base-url-from-env-extractor'
|
||||||
|
|
||||||
describe('BaseUrlFromEnvExtractor', () => {
|
describe('BaseUrlFromEnvExtractor', () => {
|
||||||
it('should return the base urls if both are valid urls', () => {
|
it('should return the base urls if all are valid urls', () => {
|
||||||
process.env.HD_BASE_URL = 'https://editor.example.org/'
|
process.env.HD_BASE_URL = 'https://editor.example.org/'
|
||||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||||
|
process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/'
|
||||||
|
|
||||||
const sut = new BaseUrlFromEnvExtractor()
|
const sut = new BaseUrlFromEnvExtractor()
|
||||||
|
|
||||||
expect(sut.extractBaseUrls()).toStrictEqual({
|
expect(sut.extractBaseUrls()).toStrictEqual({
|
||||||
renderer: 'https://renderer.example.org/',
|
renderer: 'https://renderer.example.org/',
|
||||||
|
internalApiUrl: 'https://internal.example.org/',
|
||||||
editor: 'https://editor.example.org/'
|
editor: 'https://editor.example.org/'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return an empty optional if no var is set', () => {
|
it('should throw if no var is set', () => {
|
||||||
process.env.HD_BASE_URL = undefined
|
process.env.HD_BASE_URL = undefined
|
||||||
process.env.HD_RENDERER_BASE_URL = undefined
|
process.env.HD_RENDERER_BASE_URL = undefined
|
||||||
|
process.env.HD_INTERNAL_API_URL = undefined
|
||||||
const sut = new BaseUrlFromEnvExtractor()
|
const sut = new BaseUrlFromEnvExtractor()
|
||||||
|
|
||||||
expect(() => sut.extractBaseUrls()).toThrow()
|
expect(() => sut.extractBaseUrls()).toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return an empty optional if editor base url isn't an URL", () => {
|
it("should throw if editor base url isn't an URL", () => {
|
||||||
process.env.HD_BASE_URL = 'bibedibabedibu'
|
process.env.HD_BASE_URL = 'bibedibabedibu'
|
||||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||||
|
process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/'
|
||||||
const sut = new BaseUrlFromEnvExtractor()
|
const sut = new BaseUrlFromEnvExtractor()
|
||||||
|
|
||||||
expect(() => sut.extractBaseUrls()).toThrow()
|
expect(() => sut.extractBaseUrls()).toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return an empty optional if renderer base url isn't an URL", () => {
|
it("should throw if renderer base url isn't an URL", () => {
|
||||||
process.env.HD_BASE_URL = 'https://editor.example.org/'
|
process.env.HD_BASE_URL = 'https://editor.example.org/'
|
||||||
process.env.HD_RENDERER_BASE_URL = 'bibedibabedibu'
|
process.env.HD_RENDERER_BASE_URL = 'bibedibabedibu'
|
||||||
|
process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/'
|
||||||
const sut = new BaseUrlFromEnvExtractor()
|
const sut = new BaseUrlFromEnvExtractor()
|
||||||
|
|
||||||
expect(() => sut.extractBaseUrls()).toThrow()
|
expect(() => sut.extractBaseUrls()).toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return an optional if editor base url isn't ending with a slash", () => {
|
it("should throw if internal api url isn't an URL", () => {
|
||||||
process.env.HD_BASE_URL = 'https://editor.example.org'
|
process.env.HD_BASE_URL = 'https://editor.example.org/'
|
||||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||||
|
process.env.HD_INTERNAL_API_URL = 'bibedibabedibu'
|
||||||
const sut = new BaseUrlFromEnvExtractor()
|
const sut = new BaseUrlFromEnvExtractor()
|
||||||
|
|
||||||
expect(sut.extractBaseUrls()).toStrictEqual({
|
expect(() => sut.extractBaseUrls()).toThrow()
|
||||||
renderer: 'https://renderer.example.org/',
|
|
||||||
editor: 'https://editor.example.org/'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return an optional if renderer base url isn't ending with a slash", () => {
|
it('should throw if editor base url contains a subdirectory', () => {
|
||||||
process.env.HD_BASE_URL = 'https://editor.example.org/'
|
process.env.HD_BASE_URL = 'https://editor.example.org/asd/'
|
||||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org'
|
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||||
|
process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/'
|
||||||
const sut = new BaseUrlFromEnvExtractor()
|
const sut = new BaseUrlFromEnvExtractor()
|
||||||
|
|
||||||
expect(sut.extractBaseUrls()).toStrictEqual({
|
expect(() => sut.extractBaseUrls()).toThrow()
|
||||||
renderer: 'https://renderer.example.org/',
|
|
||||||
editor: 'https://editor.example.org/'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should copy editor base url to renderer base url if renderer base url is omitted', () => {
|
it('should throw if renderer base url contains a subdirectory', () => {
|
||||||
process.env.HD_BASE_URL = 'https://editor.example.org/'
|
process.env.HD_BASE_URL = 'https://editor.example.org/'
|
||||||
|
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/asd/'
|
||||||
|
process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/'
|
||||||
|
const sut = new BaseUrlFromEnvExtractor()
|
||||||
|
|
||||||
|
expect(() => sut.extractBaseUrls()).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw if internal api url contains a subdirectory', () => {
|
||||||
|
process.env.HD_BASE_URL = 'https://editor.example.org/'
|
||||||
|
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||||
|
process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/asd/'
|
||||||
|
const sut = new BaseUrlFromEnvExtractor()
|
||||||
|
|
||||||
|
expect(() => sut.extractBaseUrls()).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should copy editor base url to renderer base/internal api url if url is omitted', () => {
|
||||||
|
process.env.HD_BASE_URL = 'https://editor1.example.org/'
|
||||||
delete process.env.HD_RENDERER_BASE_URL
|
delete process.env.HD_RENDERER_BASE_URL
|
||||||
|
delete process.env.HD_INTERNAL_API_URL
|
||||||
const sut = new BaseUrlFromEnvExtractor()
|
const sut = new BaseUrlFromEnvExtractor()
|
||||||
|
|
||||||
expect(sut.extractBaseUrls()).toStrictEqual({
|
expect(sut.extractBaseUrls()).toStrictEqual({
|
||||||
renderer: 'https://editor.example.org/',
|
renderer: 'https://editor1.example.org/',
|
||||||
editor: 'https://editor.example.org/'
|
internalApiUrl: 'https://editor1.example.org/',
|
||||||
|
editor: 'https://editor1.example.org/'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,7 +16,7 @@ export class BaseUrlFromEnvExtractor {
|
||||||
private baseUrls: BaseUrls | undefined
|
private baseUrls: BaseUrls | undefined
|
||||||
private readonly logger = new Logger('Base URL Configuration')
|
private readonly logger = new Logger('Base URL Configuration')
|
||||||
|
|
||||||
private extractUrlFromEnvVar(envVarName: string, envVarValue: string | undefined): Optional<URL> {
|
private extractUrlFromEnvVar(envVarValue: string | undefined): Optional<URL> {
|
||||||
try {
|
try {
|
||||||
return parseUrl(envVarValue)
|
return parseUrl(envVarValue)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -30,37 +30,40 @@ export class BaseUrlFromEnvExtractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractEditorBaseUrlFromEnv(): Optional<URL> {
|
private extractEditorBaseUrlFromEnv(): Optional<URL> {
|
||||||
const envValue = this.extractUrlFromEnvVar('HD_BASE_URL', process.env.HD_BASE_URL)
|
const envValue = this.extractUrlFromEnvVar(process.env.HD_BASE_URL)
|
||||||
if (envValue.isEmpty()) {
|
if (envValue.isEmpty()) {
|
||||||
this.logger.error("HD_BASE_URL isn't a valid URL!")
|
this.logger.error("HD_BASE_URL isn't a valid URL!")
|
||||||
}
|
}
|
||||||
return envValue
|
return envValue
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractRendererBaseUrlFromEnv(editorBaseUrl: URL): Optional<URL> {
|
private extractUrlFromEnv(editorBaseUrl: URL, envVarName: string): Optional<URL> {
|
||||||
if (isTestMode) {
|
if (isTestMode) {
|
||||||
this.logger.info('Test mode activated. Using editor base url for renderer.')
|
this.logger.info(`Test mode activated. Using editor base url for ${envVarName}.`)
|
||||||
return Optional.of(editorBaseUrl)
|
return Optional.of(editorBaseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.env.HD_RENDERER_BASE_URL) {
|
if (!process.env[envVarName]) {
|
||||||
this.logger.info('HD_RENDERER_BASE_URL is unset. Using editor base url for renderer.')
|
this.logger.info(`${envVarName} is unset. Using editor base url.`)
|
||||||
return Optional.of(editorBaseUrl)
|
return Optional.of(editorBaseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.extractUrlFromEnvVar('HD_RENDERER_BASE_URL', process.env.HD_RENDERER_BASE_URL)
|
return this.extractUrlFromEnvVar(process.env[envVarName])
|
||||||
}
|
}
|
||||||
|
|
||||||
private renewBaseUrls(): BaseUrls {
|
private renewBaseUrls(): BaseUrls {
|
||||||
return this.extractEditorBaseUrlFromEnv()
|
return this.extractEditorBaseUrlFromEnv()
|
||||||
.flatMap((editorBaseUrl) =>
|
.flatMap((editorBaseUrl) =>
|
||||||
this.extractRendererBaseUrlFromEnv(editorBaseUrl).map((rendererBaseUrl) => {
|
this.extractUrlFromEnv(editorBaseUrl, 'HD_RENDERER_BASE_URL').flatMap((rendererBaseUrl) =>
|
||||||
|
this.extractUrlFromEnv(editorBaseUrl, 'HD_INTERNAL_API_URL').map((internalApiUrl) => {
|
||||||
return {
|
return {
|
||||||
editor: editorBaseUrl.toString(),
|
editor: editorBaseUrl.toString(),
|
||||||
renderer: rendererBaseUrl.toString()
|
renderer: rendererBaseUrl.toString(),
|
||||||
|
internalApiUrl: internalApiUrl.toString()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
)
|
||||||
.orElseThrow(() => new Error('couldnt parse env vars'))
|
.orElseThrow(() => new Error('couldnt parse env vars'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +76,8 @@ export class BaseUrlFromEnvExtractor {
|
||||||
if (isBuildTime) {
|
if (isBuildTime) {
|
||||||
return {
|
return {
|
||||||
editor: 'https://example.org/',
|
editor: 'https://example.org/',
|
||||||
renderer: 'https://example.org/'
|
renderer: 'https://example.org/',
|
||||||
|
internalApiUrl: 'https://example.org/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +94,7 @@ export class BaseUrlFromEnvExtractor {
|
||||||
}
|
}
|
||||||
this.logger.info('Editor base URL', this.baseUrls.editor.toString())
|
this.logger.info('Editor base URL', this.baseUrls.editor.toString())
|
||||||
this.logger.info('Renderer base URL', this.baseUrls.renderer.toString())
|
this.logger.info('Renderer base URL', this.baseUrls.renderer.toString())
|
||||||
|
this.logger.info('Internal API URL', this.baseUrls.internalApiUrl.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue