diff --git a/.github/workflows/goci.yaml b/.github/workflows/goci.yaml index bdafb9499e..61aec4976c 100644 --- a/.github/workflows/goci.yaml +++ b/.github/workflows/goci.yaml @@ -73,3 +73,19 @@ jobs: shell: bash run: | make docker-build-enterprise + openapi: + if: | + (github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) || + (github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test')) + runs-on: ubuntu-latest + steps: + - name: self-checkout + uses: actions/checkout@v4 + - name: go-install + uses: actions/setup-go@v5 + with: + go-version: "1.24" + - name: generate-openapi + run: | + go run cmd/enterprise/*.go generate openapi + git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in openapi spec. Run go run cmd/enterprise/*.go generate openapi locally and commit."; exit 1) diff --git a/cmd/community/main.go b/cmd/community/main.go index e188635734..58735d8956 100644 --- a/cmd/community/main.go +++ b/cmd/community/main.go @@ -13,6 +13,7 @@ func main() { // register a list of commands to the root command registerServer(cmd.RootCmd, logger) + cmd.RegisterGenerate(cmd.RootCmd, logger) cmd.Execute(logger) } diff --git a/cmd/enterprise/main.go b/cmd/enterprise/main.go index e188635734..58735d8956 100644 --- a/cmd/enterprise/main.go +++ b/cmd/enterprise/main.go @@ -13,6 +13,7 @@ func main() { // register a list of commands to the root command registerServer(cmd.RootCmd, logger) + cmd.RegisterGenerate(cmd.RootCmd, logger) cmd.Execute(logger) } diff --git a/cmd/generate.go b/cmd/generate.go new file mode 100644 index 0000000000..83b4d99d3a --- /dev/null +++ b/cmd/generate.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "log/slog" + + "github.com/spf13/cobra" +) + +func RegisterGenerate(parentCmd *cobra.Command, logger *slog.Logger) { + var generateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate artifacts", + SilenceUsage: true, + SilenceErrors: true, + CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, + } + + registerGenerateOpenAPI(generateCmd) + + parentCmd.AddCommand(generateCmd) +} diff --git a/cmd/openapi.go b/cmd/openapi.go new file mode 100644 index 0000000000..2d98f1c733 --- /dev/null +++ b/cmd/openapi.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "context" + "log/slog" + + "github.com/SigNoz/signoz/pkg/instrumentation" + "github.com/SigNoz/signoz/pkg/signoz" + "github.com/SigNoz/signoz/pkg/version" + "github.com/spf13/cobra" +) + +func registerGenerateOpenAPI(parentCmd *cobra.Command) { + openapiCmd := &cobra.Command{ + Use: "openapi", + Short: "Generate OpenAPI schema for SigNoz", + RunE: func(currCmd *cobra.Command, args []string) error { + return runGenerateOpenAPI(currCmd.Context()) + }, + } + + parentCmd.AddCommand(openapiCmd) +} + +func runGenerateOpenAPI(ctx context.Context) error { + instrumentation, err := instrumentation.New(ctx, instrumentation.Config{Logs: instrumentation.LogsConfig{Level: slog.LevelInfo}}, version.Info, "signoz") + if err != nil { + return err + } + + openapi, err := signoz.NewOpenAPI(ctx, instrumentation) + if err != nil { + return err + } + + if err := openapi.CreateAndWrite("docs/api/openapi.yml"); err != nil { + return err + } + + return nil +} diff --git a/docs/api/openapi.yml b/docs/api/openapi.yml new file mode 100644 index 0000000000..7ed96f1d4d --- /dev/null +++ b/docs/api/openapi.yml @@ -0,0 +1,2293 @@ +openapi: 3.0.3 +info: + description: OpenTelemetry-Native Logs, Metrics and Traces in a single pane + title: SigNoz + version: "" +paths: + /api/v1/changePassword/{id}: + post: + deprecated: false + description: This endpoint changes the password by id + operationId: ChangePassword + parameters: + - in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TypesChangePasswordRequest' + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Change password + tags: + - users + /api/v1/complete/google: + get: + deprecated: false + description: This endpoint creates a session for a user using google callback + operationId: CreateSessionByGoogleCallback + responses: + "303": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AuthtypesGettableToken' + status: + type: string + type: object + description: See Other + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Create session by google callback + tags: + - sessions + /api/v1/complete/oidc: + get: + deprecated: false + description: This endpoint creates a session for a user using oidc callback + operationId: CreateSessionByOIDCCallback + responses: + "303": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AuthtypesGettableToken' + status: + type: string + type: object + description: See Other + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "451": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unavailable For Legal Reasons + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Create session by oidc callback + tags: + - sessions + /api/v1/complete/saml: + post: + deprecated: false + description: This endpoint creates a session for a user using saml callback + operationId: CreateSessionBySAMLCallback + parameters: + - in: query + name: RelayState + schema: + type: string + - in: query + name: SAMLResponse + schema: + type: string + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + RelayState: + type: string + SAMLResponse: + type: string + type: object + responses: + "303": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AuthtypesGettableToken' + status: + type: string + type: object + description: See Other + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "451": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unavailable For Legal Reasons + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Create session by saml callback + tags: + - sessions + /api/v1/domains: + get: + deprecated: false + description: This endpoint lists all auth domains + operationId: ListAuthDomains + responses: + "200": + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/AuthtypesGettableAuthDomain' + type: array + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: List all auth domains + tags: + - authdomains + post: + deprecated: false + description: This endpoint creates an auth domain + operationId: CreateAuthDomain + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AuthtypesPostableAuthDomain' + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AuthtypesGettableAuthDomain' + status: + type: string + type: object + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Conflict + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Create auth domain + tags: + - authdomains + /api/v1/domains/{id}: + delete: + deprecated: false + description: This endpoint deletes an auth domain + operationId: DeleteAuthDomain + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Delete auth domain + tags: + - authdomains + put: + deprecated: false + description: This endpoint updates an auth domain + operationId: UpdateAuthDomain + parameters: + - in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AuthtypesUpdateableAuthDomain' + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Conflict + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Update auth domain + tags: + - authdomains + /api/v1/getResetPasswordToken/{id}: + get: + deprecated: false + description: This endpoint returns the reset password token by id + operationId: GetResetPasswordToken + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/TypesResetPasswordToken' + status: + type: string + type: object + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Get reset password token + tags: + - users + /api/v1/invite: + get: + deprecated: false + description: This endpoint lists all invites + operationId: ListInvite + responses: + "200": + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/TypesInvite' + type: array + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: List invites + tags: + - users + post: + deprecated: false + description: This endpoint creates an invite for a user + operationId: CreateInvite + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TypesPostableInvite' + responses: + "201": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/TypesInvite' + status: + type: string + type: object + description: Created + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Conflict + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Create invite + tags: + - users + /api/v1/invite/{id}: + delete: + deprecated: false + description: This endpoint deletes an invite by id + operationId: DeleteInvite + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Delete invite + tags: + - users + /api/v1/invite/{token}: + get: + deprecated: false + description: This endpoint gets an invite by token + operationId: GetInvite + parameters: + - in: path + name: token + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/TypesInvite' + status: + type: string + type: object + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Get invite + tags: + - users + /api/v1/invite/accept: + post: + deprecated: false + description: This endpoint accepts an invite by token + operationId: AcceptInvite + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TypesPostableAcceptInvite' + responses: + "201": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/TypesUser' + status: + type: string + type: object + description: Created + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Accept invite + tags: + - users + /api/v1/invite/bulk: + post: + deprecated: false + description: This endpoint creates a bulk invite for a user + operationId: CreateBulkInvite + requestBody: + content: + application/json: + schema: + items: + $ref: '#/components/schemas/TypesPostableInvite' + type: array + responses: + "201": + description: Created + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Conflict + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Create bulk invite + tags: + - users + /api/v1/login: + post: + deprecated: true + description: This endpoint is deprecated and will be removed in the future + operationId: DeprecatedCreateSessionByEmailPassword + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AuthtypesDeprecatedPostableLogin' + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AuthtypesDeprecatedGettableLogin' + status: + type: string + type: object + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Deprecated create session by email password + tags: + - sessions + /api/v1/org/preferences: + get: + deprecated: false + description: This endpoint lists all org preferences + operationId: ListOrgPreferences + responses: + "200": + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/PreferencetypesPreference' + type: array + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: List org preferences + tags: + - preferences + /api/v1/org/preferences/{name}: + get: + deprecated: false + description: This endpoint returns the org preference by name + operationId: GetOrgPreference + parameters: + - in: path + name: name + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/PreferencetypesPreference' + status: + type: string + type: object + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Get org preference + tags: + - preferences + put: + deprecated: false + description: This endpoint updates the org preference by name + operationId: UpdateOrgPreference + parameters: + - in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PreferencetypesUpdatablePreference' + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Update org preference + tags: + - preferences + /api/v1/pats: + get: + deprecated: false + description: This endpoint lists all api keys + operationId: ListAPIKeys + responses: + "200": + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/TypesGettableAPIKey' + type: array + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: List api keys + tags: + - users + post: + deprecated: false + description: This endpoint creates an api key + operationId: CreateAPIKey + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TypesPostableAPIKey' + responses: + "201": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/TypesGettableAPIKey' + status: + type: string + type: object + description: Created + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Conflict + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Create api key + tags: + - users + /api/v1/pats/{id}: + delete: + deprecated: false + description: This endpoint revokes an api key + operationId: RevokeAPIKey + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "204": + description: No Content + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Revoke api key + tags: + - users + put: + deprecated: false + description: This endpoint updates an api key + operationId: UpdateAPIKey + parameters: + - in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TypesStorableAPIKey' + responses: + "204": + content: + application/json: + schema: + type: string + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Update api key + tags: + - users + /api/v1/resetPassword: + post: + deprecated: false + description: This endpoint resets the password by token + operationId: ResetPassword + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TypesPostableResetPassword' + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Conflict + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Reset password + tags: + - users + /api/v1/user: + get: + deprecated: false + description: This endpoint lists all users + operationId: ListUsers + responses: + "200": + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/TypesUser' + type: array + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: List users + tags: + - users + /api/v1/user/{id}: + delete: + deprecated: false + description: This endpoint deletes the user by id + operationId: DeleteUser + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "204": + description: No Content + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Delete user + tags: + - users + get: + deprecated: false + description: This endpoint returns the user by id + operationId: GetUser + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/TypesUser' + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Get user + tags: + - users + put: + deprecated: false + description: This endpoint updates the user by id + operationId: UpdateUser + parameters: + - in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TypesUser' + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/TypesUser' + status: + type: string + type: object + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Update user + tags: + - users + /api/v1/user/me: + get: + deprecated: false + description: This endpoint returns the user I belong to + operationId: GetMyUser + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/TypesUser' + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - tokenizer: [] + summary: Get my user + tags: + - users + /api/v1/user/preferences: + get: + deprecated: false + description: This endpoint lists all user preferences + operationId: ListUserPreferences + responses: + "200": + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/PreferencetypesPreference' + type: array + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - VIEWER + - tokenizer: + - VIEWER + summary: List user preferences + tags: + - preferences + /api/v1/user/preferences/{name}: + get: + deprecated: false + description: This endpoint returns the user preference by name + operationId: GetUserPreference + parameters: + - in: path + name: name + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/PreferencetypesPreference' + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - VIEWER + - tokenizer: + - VIEWER + summary: Get user preference + tags: + - preferences + put: + deprecated: false + description: This endpoint updates the user preference by name + operationId: UpdateUserPreference + parameters: + - in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PreferencetypesUpdatablePreference' + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - VIEWER + - tokenizer: + - VIEWER + summary: Update user preference + tags: + - preferences + /api/v2/orgs/me: + get: + deprecated: false + description: This endpoint returns the organization I belong to + operationId: GetMyOrganization + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/TypesOrganization' + status: + type: string + type: object + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Get my organization + tags: + - orgs + put: + deprecated: false + description: This endpoint updates the organization I belong to + operationId: UpdateMyOrganization + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TypesOrganization' + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Conflict + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - api_key: + - ADMIN + - tokenizer: + - ADMIN + summary: Update my organization + tags: + - orgs + /api/v2/sessions: + delete: + deprecated: false + description: This endpoint deletes the session + operationId: DeleteSession + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + security: + - tokenizer: [] + summary: Delete session + tags: + - sessions + /api/v2/sessions/context: + get: + deprecated: false + description: This endpoint returns the context for the session + operationId: GetSessionContext + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AuthtypesSessionContext' + status: + type: string + type: object + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Get session context + tags: + - sessions + /api/v2/sessions/email_password: + post: + deprecated: false + description: This endpoint creates a session for a user using email and password. + operationId: CreateSessionByEmailPassword + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AuthtypesPostableEmailPasswordSession' + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AuthtypesGettableToken' + status: + type: string + type: object + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Create session by email and password + tags: + - sessions + /api/v2/sessions/rotate: + post: + deprecated: false + description: This endpoint rotates the session + operationId: RotateSession + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AuthtypesPostableRotateToken' + responses: + "200": + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AuthtypesGettableToken' + status: + type: string + type: object + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Bad Request + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/RenderErrorResponse' + description: Internal Server Error + summary: Rotate session + tags: + - sessions +components: + schemas: + AuthtypesAuthDomainConfig: + properties: + googleAuthConfig: + $ref: '#/components/schemas/AuthtypesGoogleConfig' + oidcConfig: + $ref: '#/components/schemas/AuthtypesOIDCConfig' + samlConfig: + $ref: '#/components/schemas/AuthtypesSamlConfig' + ssoEnabled: + type: boolean + ssoType: + type: string + type: object + AuthtypesAuthNProviderInfo: + properties: + relayStatePath: + nullable: true + type: string + type: object + AuthtypesAuthNSupport: + properties: + callback: + items: + $ref: '#/components/schemas/AuthtypesCallbackAuthNSupport' + nullable: true + type: array + password: + items: + $ref: '#/components/schemas/AuthtypesPasswordAuthNSupport' + nullable: true + type: array + type: object + AuthtypesCallbackAuthNSupport: + properties: + provider: + type: string + url: + type: string + type: object + AuthtypesClaimMapping: + properties: + email: + type: string + type: object + AuthtypesDeprecatedGettableLogin: + properties: + accessJwt: + type: string + userId: + type: string + type: object + AuthtypesDeprecatedPostableLogin: + properties: + email: + type: string + password: + type: string + type: object + AuthtypesGettableAuthDomain: + properties: + authNProviderInfo: + $ref: '#/components/schemas/AuthtypesAuthNProviderInfo' + createdAt: + format: date-time + type: string + googleAuthConfig: + $ref: '#/components/schemas/AuthtypesGoogleConfig' + id: + type: string + name: + type: string + oidcConfig: + $ref: '#/components/schemas/AuthtypesOIDCConfig' + orgId: + type: string + samlConfig: + $ref: '#/components/schemas/AuthtypesSamlConfig' + ssoEnabled: + type: boolean + ssoType: + type: string + updatedAt: + format: date-time + type: string + type: object + AuthtypesGettableToken: + properties: + accessToken: + type: string + expiresIn: + type: integer + refreshToken: + type: string + tokenType: + type: string + type: object + AuthtypesGoogleConfig: + properties: + clientId: + type: string + clientSecret: + type: string + redirectURI: + type: string + type: object + AuthtypesOIDCConfig: + properties: + claimMapping: + $ref: '#/components/schemas/AuthtypesClaimMapping' + clientId: + type: string + clientSecret: + type: string + getUserInfo: + type: boolean + insecureSkipEmailVerified: + type: boolean + issuer: + type: string + issuerAlias: + type: string + type: object + AuthtypesOrgSessionContext: + properties: + authNSupport: + $ref: '#/components/schemas/AuthtypesAuthNSupport' + id: + type: string + name: + type: string + warning: + $ref: '#/components/schemas/ErrorsJSON' + type: object + AuthtypesPasswordAuthNSupport: + properties: + provider: + type: string + type: object + AuthtypesPostableAuthDomain: + properties: + config: + $ref: '#/components/schemas/AuthtypesAuthDomainConfig' + name: + type: string + type: object + AuthtypesPostableEmailPasswordSession: + properties: + email: + type: string + orgId: + type: string + password: + type: string + type: object + AuthtypesPostableRotateToken: + properties: + refreshToken: + type: string + type: object + AuthtypesSamlConfig: + properties: + insecureSkipAuthNRequestsSigned: + type: boolean + samlCert: + type: string + samlEntity: + type: string + samlIdp: + type: string + type: object + AuthtypesSessionContext: + properties: + exists: + type: boolean + orgs: + items: + $ref: '#/components/schemas/AuthtypesOrgSessionContext' + nullable: true + type: array + type: object + AuthtypesUpdateableAuthDomain: + properties: + config: + $ref: '#/components/schemas/AuthtypesAuthDomainConfig' + type: object + ErrorsJSON: + properties: + code: + type: string + errors: + items: + $ref: '#/components/schemas/ErrorsResponseerroradditional' + type: array + message: + type: string + url: + type: string + type: object + ErrorsResponseerroradditional: + properties: + message: + type: string + type: object + PreferencetypesPreference: + properties: + allowedScopes: + items: + type: string + nullable: true + type: array + allowedValues: + items: + type: string + nullable: true + type: array + defaultValue: + $ref: '#/components/schemas/PreferencetypesValue' + description: + type: string + name: + type: string + value: + $ref: '#/components/schemas/PreferencetypesValue' + valueType: + type: string + type: object + PreferencetypesUpdatablePreference: + properties: + value: {} + type: object + PreferencetypesValue: + type: object + RenderErrorResponse: + properties: + error: + $ref: '#/components/schemas/ErrorsJSON' + status: + type: string + type: object + TypesChangePasswordRequest: + properties: + newPassword: + type: string + oldPassword: + type: string + userId: + type: string + type: object + TypesGettableAPIKey: + properties: + createdAt: + format: date-time + type: string + createdBy: + type: string + createdByUser: + $ref: '#/components/schemas/TypesUser' + expiresAt: + format: int64 + type: integer + id: + type: string + lastUsed: + format: int64 + type: integer + name: + type: string + revoked: + type: boolean + role: + type: string + token: + type: string + updatedAt: + format: date-time + type: string + updatedBy: + type: string + updatedByUser: + $ref: '#/components/schemas/TypesUser' + userId: + type: string + type: object + TypesInvite: + properties: + createdAt: + format: date-time + type: string + email: + type: string + id: + type: string + inviteLink: + type: string + name: + type: string + orgId: + type: string + role: + type: string + token: + type: string + updatedAt: + format: date-time + type: string + type: object + TypesOrganization: + properties: + alias: + type: string + createdAt: + format: date-time + type: string + displayName: + type: string + id: + type: string + key: + minimum: 0 + type: integer + name: + type: string + updatedAt: + format: date-time + type: string + type: object + TypesPostableAPIKey: + properties: + expiresInDays: + format: int64 + type: integer + name: + type: string + role: + type: string + type: object + TypesPostableAcceptInvite: + properties: + displayName: + type: string + password: + type: string + sourceUrl: + type: string + token: + type: string + type: object + TypesPostableInvite: + properties: + email: + type: string + frontendBaseUrl: + type: string + name: + type: string + role: + type: string + type: object + TypesPostableResetPassword: + properties: + password: + type: string + token: + type: string + type: object + TypesResetPasswordToken: + properties: + id: + type: string + passwordId: + type: string + token: + type: string + type: object + TypesStorableAPIKey: + properties: + createdAt: + format: date-time + type: string + createdBy: + type: string + id: + type: string + name: + type: string + revoked: + type: boolean + role: + type: string + token: + type: string + updatedAt: + format: date-time + type: string + updatedBy: + type: string + userId: + type: string + type: object + TypesUser: + properties: + createdAt: + format: date-time + type: string + displayName: + type: string + email: + type: string + id: + type: string + orgId: + type: string + role: + type: string + updatedAt: + format: date-time + type: string + type: object + securitySchemes: + api_key: + description: API Keys + in: header + name: SigNoz-Api-Key + type: apiKey + tokenizer: + bearerFormat: Tokenizer + description: Tokens generated by the tokenizer + scheme: bearer + type: http diff --git a/ee/query-service/app/api/api.go b/ee/query-service/app/api/api.go index be351341c2..c99a4998b9 100644 --- a/ee/query-service/app/api/api.go +++ b/ee/query-service/app/api/api.go @@ -94,10 +94,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) { // routes available only in ee version router.HandleFunc("/api/v1/features", am.ViewAccess(ah.getFeatureFlags)).Methods(http.MethodGet) - // paid plans specific routes - router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.Signoz.Handlers.Session.CreateSessionBySAMLCallback)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/complete/oidc", am.OpenAccess(ah.Signoz.Handlers.Session.CreateSessionByOIDCCallback)).Methods(http.MethodGet) - // base overrides router.HandleFunc("/api/v1/version", am.OpenAccess(ah.getVersion)).Methods(http.MethodGet) diff --git a/ee/query-service/app/server.go b/ee/query-service/app/server.go index ce8fcdb613..25f56e571b 100644 --- a/ee/query-service/app/server.go +++ b/ee/query-service/app/server.go @@ -243,6 +243,11 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h apiHandler.MetricExplorerRoutes(r, am) apiHandler.RegisterTraceFunnelsRoutes(r, am) + err := s.signoz.APIServer.AddToRouter(r) + if err != nil { + return nil, err + } + c := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"}, @@ -253,7 +258,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h handler = handlers.CompressHandler(handler) - err := web.AddToRouter(r) + err = web.AddToRouter(r) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 35832b0573..74b70ef6fb 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,8 @@ require ( github.com/spf13/cobra v1.10.1 github.com/srikanthccv/ClickHouse-go-mock v0.12.0 github.com/stretchr/testify v1.11.1 + github.com/swaggest/jsonschema-go v0.3.78 + github.com/swaggest/rest v0.2.75 github.com/tidwall/gjson v1.18.0 github.com/uptrace/bun v1.2.9 github.com/uptrace/bun/dialect/pgdialect v1.2.9 @@ -94,6 +96,8 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.15.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/swaggest/refl v1.4.0 // indirect + github.com/swaggest/usecase v1.3.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect go.opentelemetry.io/collector/config/configretry v1.34.0 // indirect @@ -256,6 +260,7 @@ require ( github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/swaggest/openapi-go v0.2.60 github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect diff --git a/go.sum b/go.sum index 85207b2d07..7214ac8d82 100644 --- a/go.sum +++ b/go.sum @@ -158,6 +158,10 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bool64/dev v0.2.40 h1:LUSD+Aq+WB3KwVntqXstevJ0wB12ig1bEgoG8ZafsZU= +github.com/bool64/dev v0.2.40/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -578,6 +582,8 @@ github.com/huandu/go-sqlbuilder v1.35.0 h1:ESvxFHN8vxCTudY1Vq63zYpU5yJBESn19sf6k github.com/huandu/go-sqlbuilder v1.35.0/go.mod h1:mS0GAtrtW+XL6nM2/gXHRJax2RwSW1TraavWDFAc1JA= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -898,6 +904,8 @@ github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFT github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/santhosh-tekuri/jsonschema/v3 v3.1.0 h1:levPcBfnazlA1CyCMC3asL/QLZkq9pa8tQZOH513zQw= +github.com/santhosh-tekuri/jsonschema/v3 v3.1.0/go.mod h1:8kzK2TC0k0YjOForaAHdNEa7ik0fokNa2k30BKJ/W7Y= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 h1:KhF0WejiUTDbL5X55nXowP7zNopwpowa6qaMAWyIE+0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33/go.mod h1:792k1RTU+5JeMXm35/e2Wgp71qPH/DmDoZrRc+EFZDk= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= @@ -910,8 +918,8 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N++y4= github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= @@ -983,6 +991,18 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= +github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= +github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= +github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk= +github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= +github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= +github.com/swaggest/rest v0.2.75 h1:MW9zZ3d0kduJ2KdWnSYZIIrZJ1v3Kg+S7QZrDCZcXws= +github.com/swaggest/rest v0.2.75/go.mod h1:yw+PNgpNSdD6W46r60keVXdsBB+7SKt64i2qpeuBsq4= +github.com/swaggest/usecase v1.3.1 h1:JdKV30MTSsDxAXxkldLNcEn8O2uf565khyo6gr5sS+w= +github.com/swaggest/usecase v1.3.1/go.mod h1:cae3lDd5VDmM36OQcOOOdAlEDg40TiQYIp99S9ejWqA= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -1029,6 +1049,10 @@ github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgk github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go new file mode 100644 index 0000000000..92089a6051 --- /dev/null +++ b/pkg/apiserver/apiserver.go @@ -0,0 +1,13 @@ +package apiserver + +import ( + "github.com/gorilla/mux" +) + +type APIServer interface { + // Returns the mux router for the API server. Primarily used for collecting OpenAPI operations. + Router() *mux.Router + + // Adds the API server routes to an existing router. This is a backwards compatible method for adding routes to the input router. + AddToRouter(router *mux.Router) error +} diff --git a/pkg/apiserver/signozapiserver/authdomain.go b/pkg/apiserver/signozapiserver/authdomain.go new file mode 100644 index 0000000000..04b80ca2db --- /dev/null +++ b/pkg/apiserver/signozapiserver/authdomain.go @@ -0,0 +1,82 @@ +package signozapiserver + +import ( + "net/http" + + "github.com/SigNoz/signoz/pkg/http/handler" + "github.com/SigNoz/signoz/pkg/types" + "github.com/SigNoz/signoz/pkg/types/authtypes" + "github.com/gorilla/mux" +) + +func (provider *provider) addAuthDomainRoutes(router *mux.Router) error { + if err := router.Handle("/api/v1/domains", handler.New(provider.authZ.AdminAccess(provider.authDomainHandler.List), handler.OpenAPIDef{ + ID: "ListAuthDomains", + Tags: []string{"authdomains"}, + Summary: "List all auth domains", + Description: "This endpoint lists all auth domains", + Request: nil, + RequestContentType: "", + Response: make([]*authtypes.GettableAuthDomain, 0), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/domains", handler.New(provider.authZ.AdminAccess(provider.authDomainHandler.Create), handler.OpenAPIDef{ + ID: "CreateAuthDomain", + Tags: []string{"authdomains"}, + Summary: "Create auth domain", + Description: "This endpoint creates an auth domain", + Request: new(authtypes.PostableAuthDomain), + RequestContentType: "application/json", + Response: new(authtypes.GettableAuthDomain), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authZ.AdminAccess(provider.authDomainHandler.Update), handler.OpenAPIDef{ + ID: "UpdateAuthDomain", + Tags: []string{"authdomains"}, + Summary: "Update auth domain", + Description: "This endpoint updates an auth domain", + Request: new(authtypes.UpdateableAuthDomain), + RequestContentType: "application/json", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPut).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authZ.AdminAccess(provider.authDomainHandler.Delete), handler.OpenAPIDef{ + ID: "DeleteAuthDomain", + Tags: []string{"authdomains"}, + Summary: "Delete auth domain", + Description: "This endpoint deletes an auth domain", + Request: nil, + RequestContentType: "", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusBadRequest}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodDelete).GetError(); err != nil { + return err + } + + return nil +} diff --git a/pkg/apiserver/signozapiserver/org.go b/pkg/apiserver/signozapiserver/org.go new file mode 100644 index 0000000000..b521fae170 --- /dev/null +++ b/pkg/apiserver/signozapiserver/org.go @@ -0,0 +1,47 @@ +package signozapiserver + +import ( + "net/http" + + "github.com/SigNoz/signoz/pkg/http/handler" + "github.com/SigNoz/signoz/pkg/types" + "github.com/gorilla/mux" +) + +func (provider *provider) addOrgRoutes(router *mux.Router) error { + if err := router.Handle("/api/v2/orgs/me", handler.New(provider.authZ.AdminAccess(provider.orgHandler.Get), handler.OpenAPIDef{ + ID: "GetMyOrganization", + Tags: []string{"orgs"}, + Summary: "Get my organization", + Description: "This endpoint returns the organization I belong to", + Request: nil, + RequestContentType: "", + Response: new(types.Organization), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v2/orgs/me", handler.New(provider.authZ.AdminAccess(provider.orgHandler.Update), handler.OpenAPIDef{ + ID: "UpdateMyOrganization", + Tags: []string{"orgs"}, + Summary: "Update my organization", + Description: "This endpoint updates the organization I belong to", + Request: new(types.Organization), + RequestContentType: "application/json", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusConflict, http.StatusBadRequest}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPut).GetError(); err != nil { + return err + } + + return nil +} diff --git a/pkg/apiserver/signozapiserver/preference.go b/pkg/apiserver/signozapiserver/preference.go new file mode 100644 index 0000000000..411eeb63d3 --- /dev/null +++ b/pkg/apiserver/signozapiserver/preference.go @@ -0,0 +1,116 @@ +package signozapiserver + +import ( + "net/http" + + "github.com/SigNoz/signoz/pkg/http/handler" + "github.com/SigNoz/signoz/pkg/types" + "github.com/SigNoz/signoz/pkg/types/preferencetypes" + "github.com/gorilla/mux" +) + +func (provider *provider) addPreferenceRoutes(router *mux.Router) error { + if err := router.Handle("/api/v1/user/preferences", handler.New(provider.authZ.ViewAccess(provider.preferenceHandler.ListByUser), handler.OpenAPIDef{ + ID: "ListUserPreferences", + Tags: []string{"preferences"}, + Summary: "List user preferences", + Description: "This endpoint lists all user preferences", + Request: nil, + RequestContentType: "", + Response: make([]*preferencetypes.Preference, 0), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleViewer), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/user/preferences/{name}", handler.New(provider.authZ.ViewAccess(provider.preferenceHandler.GetByUser), handler.OpenAPIDef{ + ID: "GetUserPreference", + Tags: []string{"preferences"}, + Summary: "Get user preference", + Description: "This endpoint returns the user preference by name", + Request: nil, + RequestContentType: "", + Response: new(preferencetypes.Preference), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleViewer), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/user/preferences/{name}", handler.New(provider.authZ.ViewAccess(provider.preferenceHandler.UpdateByUser), handler.OpenAPIDef{ + ID: "UpdateUserPreference", + Tags: []string{"preferences"}, + Summary: "Update user preference", + Description: "This endpoint updates the user preference by name", + Request: new(preferencetypes.UpdatablePreference), + RequestContentType: "application/json", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleViewer), + })).Methods(http.MethodPut).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/org/preferences", handler.New(provider.authZ.AdminAccess(provider.preferenceHandler.ListByOrg), handler.OpenAPIDef{ + ID: "ListOrgPreferences", + Tags: []string{"preferences"}, + Summary: "List org preferences", + Description: "This endpoint lists all org preferences", + Request: nil, + RequestContentType: "", + Response: make([]*preferencetypes.Preference, 0), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/org/preferences/{name}", handler.New(provider.authZ.AdminAccess(provider.preferenceHandler.GetByOrg), handler.OpenAPIDef{ + ID: "GetOrgPreference", + Tags: []string{"preferences"}, + Summary: "Get org preference", + Description: "This endpoint returns the org preference by name", + Request: nil, + RequestContentType: "", + Response: new(preferencetypes.Preference), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/org/preferences/{name}", handler.New(provider.authZ.AdminAccess(provider.preferenceHandler.UpdateByOrg), handler.OpenAPIDef{ + ID: "UpdateOrgPreference", + Tags: []string{"preferences"}, + Summary: "Update org preference", + Description: "This endpoint updates the org preference by name", + Request: new(preferencetypes.UpdatablePreference), + RequestContentType: "application/json", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPut).GetError(); err != nil { + return err + } + + return nil +} diff --git a/pkg/apiserver/signozapiserver/provider.go b/pkg/apiserver/signozapiserver/provider.go new file mode 100644 index 0000000000..2857610be7 --- /dev/null +++ b/pkg/apiserver/signozapiserver/provider.go @@ -0,0 +1,115 @@ +package signozapiserver + +import ( + "context" + + "github.com/SigNoz/signoz/pkg/apiserver" + "github.com/SigNoz/signoz/pkg/authz" + "github.com/SigNoz/signoz/pkg/factory" + "github.com/SigNoz/signoz/pkg/http/handler" + "github.com/SigNoz/signoz/pkg/http/middleware" + "github.com/SigNoz/signoz/pkg/modules/authdomain" + "github.com/SigNoz/signoz/pkg/modules/organization" + "github.com/SigNoz/signoz/pkg/modules/preference" + "github.com/SigNoz/signoz/pkg/modules/session" + "github.com/SigNoz/signoz/pkg/modules/user" + "github.com/SigNoz/signoz/pkg/types" + "github.com/SigNoz/signoz/pkg/types/ctxtypes" + "github.com/gorilla/mux" +) + +type provider struct { + config apiserver.Config + settings factory.ScopedProviderSettings + router *mux.Router + authZ *middleware.AuthZ + orgHandler organization.Handler + userHandler user.Handler + sessionHandler session.Handler + authDomainHandler authdomain.Handler + preferenceHandler preference.Handler +} + +func NewFactory( + orgGetter organization.Getter, + authz authz.AuthZ, + orgHandler organization.Handler, + userHandler user.Handler, + sessionHandler session.Handler, + authDomainHandler authdomain.Handler, + preferenceHandler preference.Handler, +) factory.ProviderFactory[apiserver.APIServer, apiserver.Config] { + return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, providerSettings factory.ProviderSettings, config apiserver.Config) (apiserver.APIServer, error) { + return newProvider(ctx, providerSettings, config, orgGetter, authz, orgHandler, userHandler, sessionHandler, authDomainHandler, preferenceHandler) + }) +} + +func newProvider( + _ context.Context, + providerSettings factory.ProviderSettings, + config apiserver.Config, + orgGetter organization.Getter, + authz authz.AuthZ, + orgHandler organization.Handler, + userHandler user.Handler, + sessionHandler session.Handler, + authDomainHandler authdomain.Handler, + preferenceHandler preference.Handler, +) (apiserver.APIServer, error) { + settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver") + router := mux.NewRouter().UseEncodedPath() + + provider := &provider{ + config: config, + settings: settings, + router: router, + orgHandler: orgHandler, + userHandler: userHandler, + sessionHandler: sessionHandler, + authDomainHandler: authDomainHandler, + preferenceHandler: preferenceHandler, + } + + provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz) + + if err := provider.AddToRouter(router); err != nil { + return nil, err + } + + return provider, nil +} + +func (provider *provider) Router() *mux.Router { + return provider.router +} + +func (provider *provider) AddToRouter(router *mux.Router) error { + if err := provider.addOrgRoutes(router); err != nil { + return err + } + + if err := provider.addSessionRoutes(router); err != nil { + return err + } + + if err := provider.addAuthDomainRoutes(router); err != nil { + return err + } + + if err := provider.addUserRoutes(router); err != nil { + return err + } + + if err := provider.addPreferenceRoutes(router); err != nil { + return err + } + + return nil +} + +func newSecuritySchemes(role types.Role) []handler.OpenAPISecurityScheme { + return []handler.OpenAPISecurityScheme{ + {Name: ctxtypes.AuthTypeAPIKey.StringValue(), Scopes: []string{role.String()}}, + {Name: ctxtypes.AuthTypeTokenizer.StringValue(), Scopes: []string{role.String()}}, + } +} diff --git a/pkg/apiserver/signozapiserver/session.go b/pkg/apiserver/signozapiserver/session.go new file mode 100644 index 0000000000..8dd8c19c27 --- /dev/null +++ b/pkg/apiserver/signozapiserver/session.go @@ -0,0 +1,153 @@ +package signozapiserver + +import ( + "net/http" + + "github.com/SigNoz/signoz/pkg/http/handler" + "github.com/SigNoz/signoz/pkg/types/authtypes" + "github.com/SigNoz/signoz/pkg/types/ctxtypes" + "github.com/gorilla/mux" +) + +func (provider *provider) addSessionRoutes(router *mux.Router) error { + if err := router.Handle("/api/v1/login", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.DeprecatedCreateSessionByEmailPassword), handler.OpenAPIDef{ + ID: "DeprecatedCreateSessionByEmailPassword", + Tags: []string{"sessions"}, + Summary: "Deprecated create session by email password", + Description: "This endpoint is deprecated and will be removed in the future", + Request: new(authtypes.DeprecatedPostableLogin), + RequestContentType: "application/json", + Response: new(authtypes.DeprecatedGettableLogin), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusBadRequest}, + Deprecated: true, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v2/sessions/email_password", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.CreateSessionByEmailPassword), handler.OpenAPIDef{ + ID: "CreateSessionByEmailPassword", + Tags: []string{"sessions"}, + Summary: "Create session by email and password", + Description: "This endpoint creates a session for a user using email and password.", + Request: new(authtypes.PostableEmailPasswordSession), + RequestContentType: "application/json", + Response: new(authtypes.GettableToken), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v2/sessions/context", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.GetSessionContext), handler.OpenAPIDef{ + ID: "GetSessionContext", + Tags: []string{"sessions"}, + Summary: "Get session context", + Description: "This endpoint returns the context for the session", + Request: nil, + RequestContentType: "", + Response: new(authtypes.SessionContext), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusBadRequest}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v2/sessions/rotate", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.RotateSession), handler.OpenAPIDef{ + ID: "RotateSession", + Tags: []string{"sessions"}, + Summary: "Rotate session", + Description: "This endpoint rotates the session", + Request: new(authtypes.PostableRotateToken), + RequestContentType: "application/json", + Response: new(authtypes.GettableToken), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusBadRequest}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v2/sessions", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.DeleteSession), handler.OpenAPIDef{ + ID: "DeleteSession", + Tags: []string{"sessions"}, + Summary: "Delete session", + Description: "This endpoint deletes the session", + Request: nil, + RequestContentType: "", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusBadRequest}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: ctxtypes.AuthTypeTokenizer.StringValue()}}, + })).Methods(http.MethodDelete).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/complete/google", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.CreateSessionByGoogleCallback), handler.OpenAPIDef{ + ID: "CreateSessionByGoogleCallback", + Tags: []string{"sessions"}, + Summary: "Create session by google callback", + Description: "This endpoint creates a session for a user using google callback", + Request: nil, + RequestContentType: "", + Response: new(authtypes.GettableToken), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusSeeOther, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/complete/saml", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.CreateSessionBySAMLCallback), handler.OpenAPIDef{ + ID: "CreateSessionBySAMLCallback", + Tags: []string{"sessions"}, + Summary: "Create session by saml callback", + Description: "This endpoint creates a session for a user using saml callback", + Request: struct { + RelayState string `form:"RelayState"` + SAMLResponse string `form:"SAMLResponse"` + }{}, + RequestContentType: "application/x-www-form-urlencoded", + Response: new(authtypes.GettableToken), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusSeeOther, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound, http.StatusUnavailableForLegalReasons}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/complete/oidc", handler.New(provider.authZ.OpenAccess(provider.sessionHandler.CreateSessionByOIDCCallback), handler.OpenAPIDef{ + ID: "CreateSessionByOIDCCallback", + Tags: []string{"sessions"}, + Summary: "Create session by oidc callback", + Description: "This endpoint creates a session for a user using oidc callback", + Request: nil, + RequestContentType: "", + Response: new(authtypes.GettableToken), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusSeeOther, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound, http.StatusUnavailableForLegalReasons}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + return nil +} diff --git a/pkg/apiserver/signozapiserver/user.go b/pkg/apiserver/signozapiserver/user.go new file mode 100644 index 0000000000..6d0a3d33e3 --- /dev/null +++ b/pkg/apiserver/signozapiserver/user.go @@ -0,0 +1,319 @@ +package signozapiserver + +import ( + "net/http" + + "github.com/SigNoz/signoz/pkg/http/handler" + "github.com/SigNoz/signoz/pkg/types" + "github.com/SigNoz/signoz/pkg/types/ctxtypes" + "github.com/gorilla/mux" +) + +func (provider *provider) addUserRoutes(router *mux.Router) error { + if err := router.Handle("/api/v1/invite", handler.New(provider.authZ.AdminAccess(provider.userHandler.CreateInvite), handler.OpenAPIDef{ + ID: "CreateInvite", + Tags: []string{"users"}, + Summary: "Create invite", + Description: "This endpoint creates an invite for a user", + Request: new(types.PostableInvite), + RequestContentType: "application/json", + Response: new(types.Invite), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusCreated, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/invite/bulk", handler.New(provider.authZ.AdminAccess(provider.userHandler.CreateBulkInvite), handler.OpenAPIDef{ + ID: "CreateBulkInvite", + Tags: []string{"users"}, + Summary: "Create bulk invite", + Description: "This endpoint creates a bulk invite for a user", + Request: make([]*types.PostableInvite, 0), + RequestContentType: "application/json", + Response: nil, + SuccessStatusCode: http.StatusCreated, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/invite/{token}", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetInvite), handler.OpenAPIDef{ + ID: "GetInvite", + Tags: []string{"users"}, + Summary: "Get invite", + Description: "This endpoint gets an invite by token", + Request: nil, + RequestContentType: "", + Response: new(types.Invite), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/invite/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.DeleteInvite), handler.OpenAPIDef{ + ID: "DeleteInvite", + Tags: []string{"users"}, + Summary: "Delete invite", + Description: "This endpoint deletes an invite by id", + Request: nil, + RequestContentType: "", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodDelete).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/invite", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListInvite), handler.OpenAPIDef{ + ID: "ListInvite", + Tags: []string{"users"}, + Summary: "List invites", + Description: "This endpoint lists all invites", + Request: nil, + RequestContentType: "", + Response: make([]*types.Invite, 0), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/invite/accept", handler.New(provider.authZ.OpenAccess(provider.userHandler.AcceptInvite), handler.OpenAPIDef{ + ID: "AcceptInvite", + Tags: []string{"users"}, + Summary: "Accept invite", + Description: "This endpoint accepts an invite by token", + Request: new(types.PostableAcceptInvite), + RequestContentType: "application/json", + Response: new(types.User), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusCreated, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/pats", handler.New(provider.authZ.AdminAccess(provider.userHandler.CreateAPIKey), handler.OpenAPIDef{ + ID: "CreateAPIKey", + Tags: []string{"users"}, + Summary: "Create api key", + Description: "This endpoint creates an api key", + Request: new(types.PostableAPIKey), + RequestContentType: "application/json", + Response: new(types.GettableAPIKey), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusCreated, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/pats", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListAPIKeys), handler.OpenAPIDef{ + ID: "ListAPIKeys", + Tags: []string{"users"}, + Summary: "List api keys", + Description: "This endpoint lists all api keys", + Request: nil, + RequestContentType: "", + Response: make([]*types.GettableAPIKey, 0), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/pats/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.UpdateAPIKey), handler.OpenAPIDef{ + ID: "UpdateAPIKey", + Tags: []string{"users"}, + Summary: "Update api key", + Description: "This endpoint updates an api key", + Request: new(types.StorableAPIKey), + RequestContentType: "application/json", + Response: nil, + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPut).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/pats/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.RevokeAPIKey), handler.OpenAPIDef{ + ID: "RevokeAPIKey", + Tags: []string{"users"}, + Summary: "Revoke api key", + Description: "This endpoint revokes an api key", + Request: nil, + RequestContentType: "", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodDelete).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/user", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListUsers), handler.OpenAPIDef{ + ID: "ListUsers", + Tags: []string{"users"}, + Summary: "List users", + Description: "This endpoint lists all users", + Request: nil, + RequestContentType: "", + Response: make([]*types.GettableUser, 0), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/user/me", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetMyUser), handler.OpenAPIDef{ + ID: "GetMyUser", + Tags: []string{"users"}, + Summary: "Get my user", + Description: "This endpoint returns the user I belong to", + Request: nil, + RequestContentType: "", + Response: new(types.GettableUser), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: ctxtypes.AuthTypeTokenizer.StringValue()}}, + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.GetUser), handler.OpenAPIDef{ + ID: "GetUser", + Tags: []string{"users"}, + Summary: "Get user", + Description: "This endpoint returns the user by id", + Request: nil, + RequestContentType: "", + Response: new(types.GettableUser), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.UpdateUser), handler.OpenAPIDef{ + ID: "UpdateUser", + Tags: []string{"users"}, + Summary: "Update user", + Description: "This endpoint updates the user by id", + Request: new(types.User), + RequestContentType: "application/json", + Response: new(types.GettableUser), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPut).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/user/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.DeleteUser), handler.OpenAPIDef{ + ID: "DeleteUser", + Tags: []string{"users"}, + Summary: "Delete user", + Description: "This endpoint deletes the user by id", + Request: nil, + RequestContentType: "", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodDelete).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/getResetPasswordToken/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetResetPasswordToken), handler.OpenAPIDef{ + ID: "GetResetPasswordToken", + Tags: []string{"users"}, + Summary: "Get reset password token", + Description: "This endpoint returns the reset password token by id", + Request: nil, + RequestContentType: "", + Response: new(types.ResetPasswordToken), + ResponseContentType: "application/json", + SuccessStatusCode: http.StatusOK, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodGet).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/resetPassword", handler.New(provider.authZ.OpenAccess(provider.userHandler.ResetPassword), handler.OpenAPIDef{ + ID: "ResetPassword", + Tags: []string{"users"}, + Summary: "Reset password", + Description: "This endpoint resets the password by token", + Request: new(types.PostableResetPassword), + RequestContentType: "application/json", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict}, + Deprecated: false, + SecuritySchemes: []handler.OpenAPISecurityScheme{}, + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + if err := router.Handle("/api/v1/changePassword/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.ChangePassword), handler.OpenAPIDef{ + ID: "ChangePassword", + Tags: []string{"users"}, + Summary: "Change password", + Description: "This endpoint changes the password by id", + Request: new(types.ChangePasswordRequest), + RequestContentType: "application/json", + Response: nil, + ResponseContentType: "", + SuccessStatusCode: http.StatusNoContent, + ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + Deprecated: false, + SecuritySchemes: newSecuritySchemes(types.RoleAdmin), + })).Methods(http.MethodPost).GetError(); err != nil { + return err + } + + return nil +} diff --git a/pkg/http/handler/handler.go b/pkg/http/handler/handler.go new file mode 100644 index 0000000000..9d6aeeae54 --- /dev/null +++ b/pkg/http/handler/handler.go @@ -0,0 +1,88 @@ +package handler + +import ( + "net/http" + "slices" + + "github.com/SigNoz/signoz/pkg/errors" + "github.com/SigNoz/signoz/pkg/http/render" + "github.com/swaggest/openapi-go" +) + +type ServeOpenAPIFunc func(openapi.OperationContext) + +type Handler interface { + http.Handler + ServeOpenAPI(openapi.OperationContext) +} + +type handler struct { + handlerFunc http.HandlerFunc + openAPIDef OpenAPIDef +} + +func New(handlerFunc http.HandlerFunc, openAPIDef OpenAPIDef) Handler { + // Remove duplicate error status codes + openAPIDef.ErrorStatusCodes = slices.DeleteFunc(openAPIDef.ErrorStatusCodes, func(statusCode int) bool { + return statusCode == http.StatusUnauthorized || statusCode == http.StatusForbidden || statusCode == http.StatusInternalServerError + }) + + // Add internal server error + openAPIDef.ErrorStatusCodes = append(openAPIDef.ErrorStatusCodes, http.StatusInternalServerError) + + // Add unauthorized and forbidden status codes + if len(openAPIDef.SecuritySchemes) > 0 { + openAPIDef.ErrorStatusCodes = append(openAPIDef.ErrorStatusCodes, http.StatusUnauthorized, http.StatusForbidden) + } + + return &handler{ + handlerFunc: handlerFunc, + openAPIDef: openAPIDef, + } +} + +func (handler *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + handler.handlerFunc.ServeHTTP(rw, req) +} + +func (handler *handler) ServeOpenAPI(opCtx openapi.OperationContext) { + // Add meta information + opCtx.SetID(handler.openAPIDef.ID) + opCtx.SetTags(handler.openAPIDef.Tags...) + opCtx.SetSummary(handler.openAPIDef.Summary) + opCtx.SetDescription(handler.openAPIDef.Description) + opCtx.SetIsDeprecated(handler.openAPIDef.Deprecated) + + // Add security schemes + for _, securityScheme := range handler.openAPIDef.SecuritySchemes { + opCtx.AddSecurity(securityScheme.Name, securityScheme.Scopes...) + } + + // Add request structure + opCtx.AddReqStructure(handler.openAPIDef.Request, openapi.WithContentType(handler.openAPIDef.RequestContentType)) + + // Add success response + if handler.openAPIDef.Response != nil { + opCtx.AddRespStructure( + render.SuccessResponse{Status: render.StatusSuccess.String(), Data: handler.openAPIDef.Response}, + openapi.WithContentType(handler.openAPIDef.ResponseContentType), + openapi.WithHTTPStatus(handler.openAPIDef.SuccessStatusCode), + ) + } else { + opCtx.AddRespStructure( + nil, + openapi.WithContentType(handler.openAPIDef.ResponseContentType), + openapi.WithHTTPStatus(handler.openAPIDef.SuccessStatusCode), + ) + } + + // Add error responses + for _, statusCode := range handler.openAPIDef.ErrorStatusCodes { + opCtx.AddRespStructure( + render.ErrorResponse{Status: render.StatusError.String(), Error: &errors.JSON{}}, + openapi.WithContentType("application/json"), + openapi.WithHTTPStatus(statusCode), + ) + } + +} diff --git a/pkg/http/handler/openapi.go b/pkg/http/handler/openapi.go new file mode 100644 index 0000000000..222dc0604e --- /dev/null +++ b/pkg/http/handler/openapi.go @@ -0,0 +1,109 @@ +package handler + +import ( + "reflect" + + "github.com/gorilla/mux" + "github.com/swaggest/jsonschema-go" + openapigo "github.com/swaggest/openapi-go" + "github.com/swaggest/rest/openapi" +) + +// Def is the definition of an OpenAPI operation +type OpenAPIDef struct { + ID string + Tags []string + Summary string + Description string + Request any + RequestContentType string + Response any + ResponseContentType string + SuccessStatusCode int + ErrorStatusCodes []int + Deprecated bool + SecuritySchemes []OpenAPISecurityScheme +} + +type OpenAPISecurityScheme struct { + Name string + Scopes []string +} + +// Collector is a collector for OpenAPI operations +type OpenAPICollector struct { + collector *openapi.Collector +} + +func NewOpenAPICollector(reflector openapigo.Reflector) *OpenAPICollector { + c := openapi.NewCollector(reflector) + + return &OpenAPICollector{ + collector: c, + } +} + +func (c *OpenAPICollector) Walker(route *mux.Route, _ *mux.Router, _ []*mux.Route) error { + httpHandler := route.GetHandler() + + if httpHandler == nil { + return nil + } + + path, err := route.GetPathTemplate() + if err != nil && path == "" { + // If there is no path, skip the route + return nil + } + + methods, err := route.GetMethods() + if err != nil { + // If there is no methods, skip the route + return nil + } + + if handler, ok := httpHandler.(Handler); ok { + for _, method := range methods { + if err := c.collector.CollectOperation(method, path, c.collect(method, path, handler.ServeOpenAPI)); err != nil { + return err + } + } + return nil + } + + return nil +} + +func (c *OpenAPICollector) collect(method string, path string, serveOpenAPIFunc ServeOpenAPIFunc) func(oc openapigo.OperationContext) error { + return func(oc openapigo.OperationContext) error { + // Serve the OpenAPI documentation for the handler + serveOpenAPIFunc(oc) + + // If the handler has annotations, skip the collection + if c.collector.HasAnnotation(method, path) { + return nil + } + + // Automatically sanitize the method and path + _, _, pathItems, err := openapigo.SanitizeMethodPath(method, path) + if err != nil { + return err + } + + // If there are path items, add them to the request structure + if len(pathItems) > 0 { + req := jsonschema.Struct{} + for _, p := range pathItems { + req.Fields = append(req.Fields, jsonschema.Field{ + Name: "F" + p, + Tag: reflect.StructTag(`path:"` + p + `"`), + Value: "", + }) + } + + oc.AddReqStructure(req) + } + + return nil + } +} diff --git a/pkg/http/render/render.go b/pkg/http/render/render.go index f78c4a4d43..738f0d3a31 100644 --- a/pkg/http/render/render.go +++ b/pkg/http/render/render.go @@ -15,14 +15,18 @@ const ( var json = jsoniter.ConfigCompatibleWithStandardLibrary -type response struct { +type SuccessResponse struct { + Status string `json:"status"` + Data interface{} `json:"data,omitempty"` +} + +type ErrorResponse struct { Status string `json:"status"` - Data interface{} `json:"data,omitempty"` - Error *errors.JSON `json:"error,omitempty"` + Error *errors.JSON `json:"error"` } func Success(rw http.ResponseWriter, httpCode int, data interface{}) { - body, err := json.Marshal(&response{Status: StatusSuccess.s, Data: data}) + body, err := json.Marshal(&SuccessResponse{Status: StatusSuccess.s, Data: data}) if err != nil { Error(rw, err) return @@ -64,7 +68,7 @@ func Error(rw http.ResponseWriter, cause error) { httpCode = http.StatusUnavailableForLegalReasons } - body, err := json.Marshal(&response{Status: StatusError.s, Error: errors.AsJSON(cause)}) + body, err := json.Marshal(&ErrorResponse{Status: StatusError.s, Error: errors.AsJSON(cause)}) if err != nil { // this should never be the case http.Error(rw, err.Error(), http.StatusInternalServerError) diff --git a/pkg/http/render/status.go b/pkg/http/render/status.go index dc4f8720ff..64dce27645 100644 --- a/pkg/http/render/status.go +++ b/pkg/http/render/status.go @@ -7,3 +7,7 @@ var ( // Defines custom error types type status struct{ s string } + +func (s status) String() string { + return s.s +} diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 7ebd33165f..3f0e763c8b 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -575,56 +575,12 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) { router.HandleFunc("/api/v1/disks", am.ViewAccess(aH.getDisks)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/user/preferences", am.ViewAccess(aH.Signoz.Handlers.Preference.ListByUser)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/user/preferences/{name}", am.ViewAccess(aH.Signoz.Handlers.Preference.GetByUser)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/user/preferences/{name}", am.ViewAccess(aH.Signoz.Handlers.Preference.UpdateByUser)).Methods(http.MethodPut) - router.HandleFunc("/api/v1/org/preferences", am.AdminAccess(aH.Signoz.Handlers.Preference.ListByOrg)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/org/preferences/{name}", am.AdminAccess(aH.Signoz.Handlers.Preference.GetByOrg)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/org/preferences/{name}", am.AdminAccess(aH.Signoz.Handlers.Preference.UpdateByOrg)).Methods(http.MethodPut) - // Quick Filters router.HandleFunc("/api/v1/orgs/me/filters", am.ViewAccess(aH.Signoz.Handlers.QuickFilter.GetQuickFilters)).Methods(http.MethodGet) router.HandleFunc("/api/v1/orgs/me/filters/{signal}", am.ViewAccess(aH.Signoz.Handlers.QuickFilter.GetSignalFilters)).Methods(http.MethodGet) router.HandleFunc("/api/v1/orgs/me/filters", am.AdminAccess(aH.Signoz.Handlers.QuickFilter.UpdateQuickFilters)).Methods(http.MethodPut) - // === Authentication APIs === - router.HandleFunc("/api/v1/invite", am.AdminAccess(aH.Signoz.Handlers.User.CreateInvite)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/invite/bulk", am.AdminAccess(aH.Signoz.Handlers.User.CreateBulkInvite)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(aH.Signoz.Handlers.User.GetInvite)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/invite/{id}", am.AdminAccess(aH.Signoz.Handlers.User.DeleteInvite)).Methods(http.MethodDelete) - router.HandleFunc("/api/v1/invite", am.AdminAccess(aH.Signoz.Handlers.User.ListInvite)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/invite/accept", am.OpenAccess(aH.Signoz.Handlers.User.AcceptInvite)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/register", am.OpenAccess(aH.registerUser)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/login", am.OpenAccess(aH.Signoz.Handlers.Session.DeprecatedCreateSessionByEmailPassword)).Methods(http.MethodPost) - router.HandleFunc("/api/v2/sessions/email_password", am.OpenAccess(aH.Signoz.Handlers.Session.CreateSessionByEmailPassword)).Methods(http.MethodPost) - router.HandleFunc("/api/v2/sessions/context", am.OpenAccess(aH.Signoz.Handlers.Session.GetSessionContext)).Methods(http.MethodGet) - router.HandleFunc("/api/v2/sessions/rotate", am.OpenAccess(aH.Signoz.Handlers.Session.RotateSession)).Methods(http.MethodPost) - router.HandleFunc("/api/v2/sessions", am.OpenAccess(aH.Signoz.Handlers.Session.DeleteSession)).Methods(http.MethodDelete) - router.HandleFunc("/api/v1/complete/google", am.OpenAccess(aH.Signoz.Handlers.Session.CreateSessionByGoogleCallback)).Methods(http.MethodGet) - - router.HandleFunc("/api/v1/domains", am.AdminAccess(aH.Signoz.Handlers.AuthDomain.List)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/domains", am.AdminAccess(aH.Signoz.Handlers.AuthDomain.Create)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/domains/{id}", am.AdminAccess(aH.Signoz.Handlers.AuthDomain.Update)).Methods(http.MethodPut) - router.HandleFunc("/api/v1/domains/{id}", am.AdminAccess(aH.Signoz.Handlers.AuthDomain.Delete)).Methods(http.MethodDelete) - - router.HandleFunc("/api/v1/pats", am.AdminAccess(aH.Signoz.Handlers.User.CreateAPIKey)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/pats", am.AdminAccess(aH.Signoz.Handlers.User.ListAPIKeys)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(aH.Signoz.Handlers.User.UpdateAPIKey)).Methods(http.MethodPut) - router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(aH.Signoz.Handlers.User.RevokeAPIKey)).Methods(http.MethodDelete) - - router.HandleFunc("/api/v1/user", am.AdminAccess(aH.Signoz.Handlers.User.ListUsers)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/user/me", am.OpenAccess(aH.Signoz.Handlers.User.GetMyUser)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/user/{id}", am.SelfAccess(aH.Signoz.Handlers.User.GetUser)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/user/{id}", am.SelfAccess(aH.Signoz.Handlers.User.UpdateUser)).Methods(http.MethodPut) - router.HandleFunc("/api/v1/user/{id}", am.AdminAccess(aH.Signoz.Handlers.User.DeleteUser)).Methods(http.MethodDelete) - - router.HandleFunc("/api/v2/orgs/me", am.AdminAccess(aH.Signoz.Handlers.Organization.Get)).Methods(http.MethodGet) - router.HandleFunc("/api/v2/orgs/me", am.AdminAccess(aH.Signoz.Handlers.Organization.Update)).Methods(http.MethodPut) - - router.HandleFunc("/api/v1/getResetPasswordToken/{id}", am.AdminAccess(aH.Signoz.Handlers.User.GetResetPasswordToken)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/resetPassword", am.OpenAccess(aH.Signoz.Handlers.User.ResetPassword)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/changePassword/{id}", am.SelfAccess(aH.Signoz.Handlers.User.ChangePassword)).Methods(http.MethodPost) router.HandleFunc("/api/v3/licenses", am.ViewAccess(func(rw http.ResponseWriter, req *http.Request) { render.Success(rw, http.StatusOK, []any{}) diff --git a/pkg/query-service/app/server.go b/pkg/query-service/app/server.go index a3400da342..5b466089c5 100644 --- a/pkg/query-service/app/server.go +++ b/pkg/query-service/app/server.go @@ -223,6 +223,11 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server, api.MetricExplorerRoutes(r, am) api.RegisterTraceFunnelsRoutes(r, am) + err := s.signoz.APIServer.AddToRouter(r) + if err != nil { + return nil, err + } + c := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"}, @@ -233,7 +238,7 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server, handler = handlers.CompressHandler(handler) - err := web.AddToRouter(r) + err = web.AddToRouter(r) if err != nil { return nil, err } diff --git a/pkg/signoz/handler.go b/pkg/signoz/handler.go index 54d7bad5a6..1baaa622fc 100644 --- a/pkg/signoz/handler.go +++ b/pkg/signoz/handler.go @@ -5,16 +5,10 @@ import ( "github.com/SigNoz/signoz/pkg/licensing" "github.com/SigNoz/signoz/pkg/modules/apdex" "github.com/SigNoz/signoz/pkg/modules/apdex/implapdex" - "github.com/SigNoz/signoz/pkg/modules/authdomain" - "github.com/SigNoz/signoz/pkg/modules/authdomain/implauthdomain" "github.com/SigNoz/signoz/pkg/modules/dashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard" "github.com/SigNoz/signoz/pkg/modules/metricsexplorer" "github.com/SigNoz/signoz/pkg/modules/metricsexplorer/implmetricsexplorer" - "github.com/SigNoz/signoz/pkg/modules/organization" - "github.com/SigNoz/signoz/pkg/modules/organization/implorganization" - "github.com/SigNoz/signoz/pkg/modules/preference" - "github.com/SigNoz/signoz/pkg/modules/preference/implpreference" "github.com/SigNoz/signoz/pkg/modules/quickfilter" "github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter" "github.com/SigNoz/signoz/pkg/modules/rawdataexport" @@ -23,29 +17,20 @@ import ( "github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview" "github.com/SigNoz/signoz/pkg/modules/services" "github.com/SigNoz/signoz/pkg/modules/services/implservices" - "github.com/SigNoz/signoz/pkg/modules/session" - "github.com/SigNoz/signoz/pkg/modules/session/implsession" "github.com/SigNoz/signoz/pkg/modules/spanpercentile" "github.com/SigNoz/signoz/pkg/modules/spanpercentile/implspanpercentile" "github.com/SigNoz/signoz/pkg/modules/tracefunnel" "github.com/SigNoz/signoz/pkg/modules/tracefunnel/impltracefunnel" - "github.com/SigNoz/signoz/pkg/modules/user" - "github.com/SigNoz/signoz/pkg/modules/user/impluser" "github.com/SigNoz/signoz/pkg/querier" ) type Handlers struct { - Organization organization.Handler - Preference preference.Handler - User user.Handler SavedView savedview.Handler Apdex apdex.Handler Dashboard dashboard.Handler QuickFilter quickfilter.Handler TraceFunnel tracefunnel.Handler RawDataExport rawdataexport.Handler - AuthDomain authdomain.Handler - Session session.Handler SpanPercentile spanpercentile.Handler Services services.Handler MetricsExplorer metricsexplorer.Handler @@ -53,17 +38,12 @@ type Handlers struct { func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, querier querier.Querier, licensing licensing.Licensing) Handlers { return Handlers{ - Organization: implorganization.NewHandler(modules.OrgGetter, modules.OrgSetter), - Preference: implpreference.NewHandler(modules.Preference), - User: impluser.NewHandler(modules.User, modules.UserGetter), SavedView: implsavedview.NewHandler(modules.SavedView), Apdex: implapdex.NewHandler(modules.Apdex), Dashboard: impldashboard.NewHandler(modules.Dashboard, providerSettings, querier, licensing), QuickFilter: implquickfilter.NewHandler(modules.QuickFilter), TraceFunnel: impltracefunnel.NewHandler(modules.TraceFunnel), RawDataExport: implrawdataexport.NewHandler(modules.RawDataExport), - AuthDomain: implauthdomain.NewHandler(modules.AuthDomain), - Session: implsession.NewHandler(modules.Session), Services: implservices.NewHandler(modules.Services), MetricsExplorer: implmetricsexplorer.NewHandler(modules.MetricsExplorer), SpanPercentile: implspanpercentile.NewHandler(modules.SpanPercentile), diff --git a/pkg/signoz/openapi.go b/pkg/signoz/openapi.go new file mode 100644 index 0000000000..b797a2da53 --- /dev/null +++ b/pkg/signoz/openapi.go @@ -0,0 +1,83 @@ +package signoz + +import ( + "context" + "os" + "reflect" + + "github.com/SigNoz/signoz/pkg/apiserver" + "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver" + "github.com/SigNoz/signoz/pkg/authz" + "github.com/SigNoz/signoz/pkg/http/handler" + "github.com/SigNoz/signoz/pkg/instrumentation" + "github.com/SigNoz/signoz/pkg/modules/authdomain" + "github.com/SigNoz/signoz/pkg/modules/organization" + "github.com/SigNoz/signoz/pkg/modules/preference" + "github.com/SigNoz/signoz/pkg/modules/session" + "github.com/SigNoz/signoz/pkg/modules/user" + "github.com/SigNoz/signoz/pkg/types/ctxtypes" + "github.com/swaggest/jsonschema-go" + "github.com/swaggest/openapi-go" + "github.com/swaggest/openapi-go/openapi3" +) + +type OpenAPI struct { + apiserver apiserver.APIServer + reflector *openapi3.Reflector + collector *handler.OpenAPICollector +} + +func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumentation) (*OpenAPI, error) { + apiserver, err := signozapiserver.NewFactory( + struct{ organization.Getter }{}, + struct{ authz.AuthZ }{}, + struct{ organization.Handler }{}, + struct{ user.Handler }{}, + struct{ session.Handler }{}, + struct{ authdomain.Handler }{}, + struct{ preference.Handler }{}, + ).New(ctx, instrumentation.ToProviderSettings(), apiserver.Config{}) + if err != nil { + return nil, err + } + + reflector := openapi3.NewReflector() + reflector.JSONSchemaReflector().DefaultOptions = append(reflector.JSONSchemaReflector().DefaultOptions, jsonschema.InterceptDefName(func(t reflect.Type, defaultDefName string) string { + if defaultDefName == "RenderSuccessResponse" { + field, ok := t.FieldByName("Data") + if !ok { + return defaultDefName + } + + return field.Type.Name() + } + + return defaultDefName + })) + + reflector.SpecSchema().SetTitle("SigNoz") + reflector.SpecSchema().SetDescription("OpenTelemetry-Native Logs, Metrics and Traces in a single pane") + reflector.SpecSchema().SetAPIKeySecurity(ctxtypes.AuthTypeAPIKey.StringValue(), "SigNoz-Api-Key", openapi.InHeader, "API Keys") + reflector.SpecSchema().SetHTTPBearerTokenSecurity(ctxtypes.AuthTypeTokenizer.StringValue(), "Tokenizer", "Tokens generated by the tokenizer") + + collector := handler.NewOpenAPICollector(reflector) + + return &OpenAPI{ + apiserver: apiserver, + reflector: reflector, + collector: collector, + }, nil +} + +func (openapi *OpenAPI) CreateAndWrite(path string) error { + if err := openapi.apiserver.Router().Walk(openapi.collector.Walker); err != nil { + return err + } + + spec, err := openapi.reflector.Spec.MarshalYAML() + if err != nil { + return err + } + + return os.WriteFile(path, spec, 0o600) +} diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go index 2ff973812e..d8fa7cd3ab 100644 --- a/pkg/signoz/provider.go +++ b/pkg/signoz/provider.go @@ -8,6 +8,9 @@ import ( "github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/analytics/noopanalytics" "github.com/SigNoz/signoz/pkg/analytics/segmentanalytics" + "github.com/SigNoz/signoz/pkg/apiserver" + "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver" + "github.com/SigNoz/signoz/pkg/authz" "github.com/SigNoz/signoz/pkg/cache" "github.com/SigNoz/signoz/pkg/cache/memorycache" "github.com/SigNoz/signoz/pkg/cache/rediscache" @@ -15,8 +18,13 @@ import ( "github.com/SigNoz/signoz/pkg/emailing/noopemailing" "github.com/SigNoz/signoz/pkg/emailing/smtpemailing" "github.com/SigNoz/signoz/pkg/factory" + "github.com/SigNoz/signoz/pkg/modules/authdomain/implauthdomain" "github.com/SigNoz/signoz/pkg/modules/organization" + "github.com/SigNoz/signoz/pkg/modules/organization/implorganization" + "github.com/SigNoz/signoz/pkg/modules/preference/implpreference" + "github.com/SigNoz/signoz/pkg/modules/session/implsession" "github.com/SigNoz/signoz/pkg/modules/user" + "github.com/SigNoz/signoz/pkg/modules/user/impluser" "github.com/SigNoz/signoz/pkg/prometheus" "github.com/SigNoz/signoz/pkg/prometheus/clickhouseprometheus" "github.com/SigNoz/signoz/pkg/querier" @@ -213,6 +221,20 @@ func NewQuerierProviderFactories(telemetryStore telemetrystore.TelemetryStore, p ) } +func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.AuthZ, modules Modules, handlers Handlers) factory.NamedMap[factory.ProviderFactory[apiserver.APIServer, apiserver.Config]] { + return factory.MustNewNamedMap( + signozapiserver.NewFactory( + orgGetter, + authz, + implorganization.NewHandler(modules.OrgGetter, modules.OrgSetter), + impluser.NewHandler(modules.User, modules.UserGetter), + implsession.NewHandler(modules.Session), + implauthdomain.NewHandler(modules.AuthDomain), + implpreference.NewHandler(modules.Preference), + ), + ) +} + func NewTokenizerProviderFactories(cache cache.Cache, sqlstore sqlstore.SQLStore, orgGetter organization.Getter) factory.NamedMap[factory.ProviderFactory[tokenizer.Tokenizer, tokenizer.Config]] { tokenStore := sqltokenizerstore.NewStore(sqlstore) return factory.MustNewNamedMap( diff --git a/pkg/signoz/provider_test.go b/pkg/signoz/provider_test.go index f2e683dde3..2601aa76e2 100644 --- a/pkg/signoz/provider_test.go +++ b/pkg/signoz/provider_test.go @@ -78,4 +78,13 @@ func TestNewProviderFactories(t *testing.T) { telemetryStore := telemetrystoretest.New(telemetrystore.Config{Provider: "clickhouse"}, sqlmock.QueryMatcherEqual) NewStatsReporterProviderFactories(telemetryStore, []statsreporter.StatsCollector{}, orgGetter, userGetter, tokenizertest.New(), version.Build{}, analytics.Config{Enabled: true}) }) + + assert.NotPanics(t, func() { + NewAPIServerProviderFactories( + implorganization.NewGetter(implorganization.NewStore(sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual)), nil), + nil, + Modules{}, + Handlers{}, + ) + }) } diff --git a/pkg/signoz/signoz.go b/pkg/signoz/signoz.go index 5eabe87929..6359b68d63 100644 --- a/pkg/signoz/signoz.go +++ b/pkg/signoz/signoz.go @@ -7,6 +7,7 @@ import ( "github.com/SigNoz/signoz/pkg/alertmanager/nfmanager" "github.com/SigNoz/signoz/pkg/alertmanager/nfmanager/nfroutingstore/sqlroutingstore" "github.com/SigNoz/signoz/pkg/analytics" + "github.com/SigNoz/signoz/pkg/apiserver" "github.com/SigNoz/signoz/pkg/authn" "github.com/SigNoz/signoz/pkg/authn/authnstore/sqlauthnstore" "github.com/SigNoz/signoz/pkg/authz" @@ -54,6 +55,7 @@ type SigNoz struct { Prometheus prometheus.Prometheus Alertmanager alertmanager.Alertmanager Querier querier.Querier + APIServer apiserver.APIServer Zeus zeus.Zeus Licensing licensing.Licensing Emailing emailing.Emailing @@ -349,6 +351,18 @@ func New( // Initialize all handlers for the modules handlers := NewHandlers(modules, providerSettings, querier, licensing) + // Initialize the API server + apiserver, err := factory.NewProviderFromNamedMap( + ctx, + providerSettings, + config.APIServer, + NewAPIServerProviderFactories(orgGetter, authz, modules, handlers), + "signoz", + ) + if err != nil { + return nil, err + } + // Create a list of all stats collectors statsCollectors := []statsreporter.StatsCollector{ alertmanager, @@ -399,6 +413,7 @@ func New( Prometheus: prometheus, Alertmanager: alertmanager, Querier: querier, + APIServer: apiserver, Zeus: zeus, Licensing: licensing, Emailing: emailing, diff --git a/pkg/web/web.go b/pkg/web/web.go index ace1005506..2abbc66233 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -10,6 +10,7 @@ import ( type Web interface { // AddToRouter adds the web routes to an existing router. AddToRouter(router *mux.Router) error + // ServeHTTP serves the web routes. http.Handler }