mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-12-21 06:51:19 +08:00
feat(front+backend): various swagger improvements
This commit is contained in:
parent
52eaf1e56b
commit
1ebbd816e5
@ -1,11 +1,14 @@
|
||||
package io.papermc.hangar.config;
|
||||
|
||||
import io.papermc.hangar.controller.extras.pagination.FilterRegistry;
|
||||
import io.papermc.hangar.controller.extras.pagination.SorterRegistry;
|
||||
import io.papermc.hangar.controller.extras.pagination.annotations.ApplicableFilters;
|
||||
import io.papermc.hangar.controller.extras.pagination.annotations.ApplicableSorters;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.media.StringSchema;
|
||||
import io.swagger.v3.oas.models.parameters.Parameter;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springdoc.core.customizers.OpenApiCustomizer;
|
||||
import org.springdoc.core.customizers.OperationCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -21,40 +24,40 @@ public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
OpenApiCustomizer apiInfo() {
|
||||
return (openApi) -> openApi
|
||||
.info(new Info().title("Hangar API")
|
||||
.description("""
|
||||
This page describes the format for the current Hangar REST API as well as general usage guidelines.<br>
|
||||
Note that all routes **not** listed here should be considered **internal**, and can change at a moment's notice. **Do not use them**.
|
||||
return openApi -> {
|
||||
openApi.info(new Info().title("Hangar API").version("1.0").description("""
|
||||
This page describes the format for the current Hangar REST API as well as general usage guidelines.<br>
|
||||
Note that all routes **not** listed here should be considered **internal**, and can change at a moment's notice. **Do not use them**.
|
||||
|
||||
<h2>Authentication and Authorization</h2>
|
||||
There are two ways to consume the API: Authenticated or anonymous.
|
||||
## Authentication and Authorization
|
||||
There are two ways to consume the API: Authenticated or anonymous.
|
||||
|
||||
<h3>Anonymous</h3>
|
||||
When using anonymous authentication, you only have access to public information, but you don't need to worry about creating and storing an API key or handing JWTs.
|
||||
### Anonymous
|
||||
When using anonymous authentication, you only have access to public information, but you don't need to worry about creating and storing an API key or handing JWTs.
|
||||
|
||||
<h3>Authenticated</h3>
|
||||
If you need access to non-public content or actions, you need to create and use API keys.
|
||||
These can be created by going to the API keys page via the profile dropdown or by going to your user page and clicking on the key icon.<br><br>
|
||||
### Authenticated
|
||||
If you need access to non-public content or actions, you need to create and use API keys.
|
||||
These can be created by going to the API keys page via the profile dropdown or by going to your user page and clicking on the key icon.
|
||||
|
||||
API keys allow you to impersonate yourself, so they should be handled like passwords. **Do not share them with anyone else!**
|
||||
API keys allow you to impersonate yourself, so they should be handled like passwords. **Do not share them with anyone else!**
|
||||
|
||||
<h4>Getting and Using a JWT</h4>
|
||||
Once you have an API key, you need to authenticate yourself: Send a `POST` request with your API key identifier (a UUID) to `/api/v1/authenticate?apiKey=yourKey`. The response will contain your JWT as well as an expiration time.
|
||||
Put this JWT into the `Authentication` header of every request and make sure to request a new JWT after the expiration time has passed.<br><br>
|
||||
#### Getting and Using a JWT
|
||||
Once you have an API key, you need to authenticate yourself: Send a `POST` request with your API key identifier (a UUID) to `/api/v1/authenticate?apiKey=yourKey`. The response will contain your JWT as well as an expiration time.
|
||||
Put this JWT into the `Authentication` header of every request and make sure to request a new JWT after the expiration time has passed.
|
||||
|
||||
Please also set a meaningful `User-Agent` header. This allows us to better identify loads and needs for potentially new endpoints.
|
||||
Please also set a meaningful `User-Agent` header. This allows us to better identify loads and needs for potentially new endpoints.
|
||||
|
||||
<h2>Misc</h2>
|
||||
<h3>Date Formats</h3>
|
||||
Standard ISO types. Where possible, we use the [OpenAPI format modifier](https://swagger.io/docs/specification/data-models/data-types/#format).
|
||||
## Misc
|
||||
### Date Formats
|
||||
Standard ISO types. Where possible, we use the [OpenAPI format modifier](https://swagger.io/docs/specification/data-models/data-types/#format).
|
||||
|
||||
<h3>Rate Limits and Caching</h3>
|
||||
The default rate limit is set at 20 requests every 5 seconds with an initial overdraft for extra leniency.
|
||||
Individual endpoints, such as version creation, may have stricter rate limiting.<br><br>
|
||||
### Rate Limits and Caching
|
||||
The default rate limit is set at 20 requests every 5 seconds with an initial overdraft for extra leniency.
|
||||
Individual endpoints, such as version creation, may have stricter rate limiting.
|
||||
|
||||
If applicable, always cache responses. The Hangar API itself is cached by CloudFlare and internally.""")
|
||||
.version("1.0"));
|
||||
If applicable, always cache responses. The Hangar API itself is cached by CloudFlare and internally."""));
|
||||
openApi.getComponents().addSecuritySchemes("HangarAuth", new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT"));
|
||||
};
|
||||
}
|
||||
|
||||
// TODO fix
|
||||
@ -79,12 +82,17 @@ public class SwaggerConfig {
|
||||
public Operation customize(final Operation operation, final HandlerMethod handlerMethod) {
|
||||
final ApplicableSorters sorters = handlerMethod.getMethodAnnotation(ApplicableSorters.class);
|
||||
if (sorters != null) {
|
||||
final StringSchema allowedValues = new StringSchema();
|
||||
for (final SorterRegistry sorterRegistry : sorters.value()) {
|
||||
allowedValues.addEnumItem(sorterRegistry.getName());
|
||||
}
|
||||
operation.addParametersItem(new Parameter()
|
||||
.name("sort")
|
||||
.in("query")
|
||||
.description("Used to sort the result"));
|
||||
// TODO fix
|
||||
//.query(q -> q.style(ParameterStyle.SIMPLE).model(m -> m.scalarModel(ScalarType.STRING)).enumerationFacet(e -> e.allowedValues(Arrays.asList(sorters.value()).stream().map(SorterRegistry::getName).collect(Collectors.toSet())))).build());
|
||||
.description("Used to sort the result")
|
||||
.style(Parameter.StyleEnum.SIMPLE)
|
||||
.schema(allowedValues)
|
||||
);
|
||||
}
|
||||
final ApplicableFilters filters = handlerMethod.getMethodAnnotation(ApplicableFilters.class);
|
||||
if (filters != null) {
|
||||
@ -94,9 +102,9 @@ public class SwaggerConfig {
|
||||
operation.addParametersItem(new Parameter()
|
||||
.name(filter.getSingleQueryParam()) // TODO multi-param filters
|
||||
.in("query")
|
||||
.description(filter.getDescription()));
|
||||
// TODO fix
|
||||
// .query(q -> q.style(ParameterStyle.SIMPLE).model(m -> m.scalarModel(ScalarType.STRING))).build());
|
||||
.description(filter.getDescription())
|
||||
.style(Parameter.StyleEnum.SIMPLE)
|
||||
.schema(new StringSchema()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ public interface IApiKeysController {
|
||||
summary = "Creates an API key",
|
||||
operationId = "createKey",
|
||||
description = "Creates an API key. Requires the `edit_api_keys` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "edit_api_keys"),
|
||||
tags = "API Keys"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -42,7 +42,7 @@ public interface IApiKeysController {
|
||||
summary = "Fetches a list of API Keys",
|
||||
operationId = "getKeys",
|
||||
description = "Fetches a list of API Keys. Requires the `edit_api_keys` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "edit_api_keys"),
|
||||
tags = "API Keys"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -56,7 +56,7 @@ public interface IApiKeysController {
|
||||
summary = "Deletes an API key",
|
||||
operationId = "deleteKey",
|
||||
description = "Deletes an API key. Requires the `edit_api_keys` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "edit_api_keys"),
|
||||
tags = "API Keys"
|
||||
)
|
||||
@ApiResponses({
|
||||
|
@ -5,7 +5,6 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -21,7 +20,6 @@ public interface IAuthenticationController {
|
||||
summary = "Creates an API JWT",
|
||||
operationId = "authenticate",
|
||||
description = "`Log-in` with your API key in order to be able to call other endpoints authenticated. The returned JWT should be specified as a header in all following requests: `Authorization: HangarAuth your.jwt`",
|
||||
security = @SecurityRequirement(name = "Key"),
|
||||
tags = "Authentication"
|
||||
)
|
||||
@ApiResponses({
|
||||
|
@ -25,7 +25,7 @@ public interface IPermissionsController {
|
||||
summary = "Checks whether you have all the provided permissions",
|
||||
operationId = "hasAll",
|
||||
description = "Checks whether you have all the provided permissions in the given context",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth"),
|
||||
tags = "Permissions"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -44,7 +44,7 @@ public interface IPermissionsController {
|
||||
summary = "Checks whether you have at least one of the provided permissions",
|
||||
operationId = "hasAny",
|
||||
description = "Checks whether you have at least one of the provided permissions in the given context",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth"),
|
||||
tags = "Permissions"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -63,7 +63,7 @@ public interface IPermissionsController {
|
||||
summary = "Returns your permissions",
|
||||
operationId = "showPermissions",
|
||||
description = "Returns a list of permissions you have in the given context",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth"),
|
||||
tags = "Permissions"
|
||||
)
|
||||
@ApiResponses({
|
||||
|
@ -31,7 +31,7 @@ public interface IProjectsController {
|
||||
summary = "Returns info on a specific project",
|
||||
operationId = "getProject",
|
||||
description = "Returns info on a specific project. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Projects"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -47,7 +47,7 @@ public interface IProjectsController {
|
||||
summary = "Returns the members of a project",
|
||||
operationId = "getProjectMembers",
|
||||
description = "Returns the members of a project. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Projects"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -66,7 +66,7 @@ public interface IProjectsController {
|
||||
summary = "Searches the projects on Hangar",
|
||||
operationId = "getProjects",
|
||||
description = "Searches all the projects on Hangar, or for a single user. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Projects"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -85,7 +85,7 @@ public interface IProjectsController {
|
||||
summary = "Returns the stats for a project",
|
||||
operationId = "showProjectStats",
|
||||
description = "Returns the stats (downloads and views) for a project per day for a certain date range. Requires the `is_subject_member` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "is_subject_member"),
|
||||
tags = "Projects"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -104,7 +104,7 @@ public interface IProjectsController {
|
||||
summary = "Returns the stargazers of a project",
|
||||
operationId = "getProjectStargazers",
|
||||
description = "Returns the stargazers of a project. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Projects"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -123,7 +123,7 @@ public interface IProjectsController {
|
||||
summary = "Returns the watchers of a project",
|
||||
operationId = "getProjectWatchers",
|
||||
description = "Returns the watchers of a project. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Projects"
|
||||
)
|
||||
@ApiResponses({
|
||||
|
@ -29,7 +29,7 @@ public interface IUsersController {
|
||||
summary = "Returns a specific user",
|
||||
operationId = "getUser",
|
||||
description = "Returns a specific user. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Users"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -43,8 +43,8 @@ public interface IUsersController {
|
||||
@Operation(
|
||||
summary = "Searches for users",
|
||||
operationId = "showUsers",
|
||||
description = "Returns a list of users based on a search query",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
description = "Returns a list of users based on a search query. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Users"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -60,7 +60,7 @@ public interface IUsersController {
|
||||
summary = "Returns the starred projects for a specific user",
|
||||
operationId = "showStarred",
|
||||
description = "Returns the starred projects for a specific user. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Users"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -77,7 +77,7 @@ public interface IUsersController {
|
||||
summary = "Returns the watched projects for a specific user",
|
||||
operationId = "getUserWatching",
|
||||
description = "Returns the watched projects for a specific user. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Users"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -94,7 +94,7 @@ public interface IUsersController {
|
||||
summary = "Returns the pinned projects for a specific user",
|
||||
operationId = "getUserPinnedProjects",
|
||||
description = "Returns the pinned projects for a specific user. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Users"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -109,7 +109,7 @@ public interface IUsersController {
|
||||
summary = "Returns all users with at least one public project",
|
||||
operationId = "getAuthors",
|
||||
description = "Returns all users that have at least one public project. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Users"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -124,7 +124,7 @@ public interface IUsersController {
|
||||
summary = "Returns Hangar staff",
|
||||
operationId = "getStaff",
|
||||
description = "Returns Hanagr staff. Requires the `view_public_info` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Users"
|
||||
)
|
||||
@ApiResponses({
|
||||
|
@ -36,7 +36,7 @@ public interface IVersionsController {
|
||||
summary ="Creates a new version",
|
||||
operationId = "uploadVersion",
|
||||
description = "Creates a new version for a project. Requires the `create_version` permission in the project or owning organization.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "create_version"),
|
||||
tags = "Versions"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -54,8 +54,8 @@ public interface IVersionsController {
|
||||
summary = "Returns a specific version of a project",
|
||||
operationId = "showVersion",
|
||||
description = "Returns a specific version of a project. Requires the `view_public_info` permission in the project or owning organization.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
tags = "Version"
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Versions"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Ok"),
|
||||
@ -71,7 +71,7 @@ public interface IVersionsController {
|
||||
summary = "Returns all versions of a project",
|
||||
operationId = "listVersions",
|
||||
description = "Returns all versions of a project. Requires the `view_public_info` permission in the project or owning organization.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "view_public_info"),
|
||||
tags = "Versions"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -88,7 +88,7 @@ public interface IVersionsController {
|
||||
summary = "Returns the stats for a version",
|
||||
operationId = "showVersionStats",
|
||||
description = "Returns the stats (downloads) for a version per day for a certain date range. Requires the `is_subject_member` permission.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth", scopes = "is_subject_member"),
|
||||
tags = "Versions"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ -108,7 +108,7 @@ public interface IVersionsController {
|
||||
summary = "Downloads a version",
|
||||
operationId = "downloadVersion",
|
||||
description = "Downloads the file for a specific platform of a version. Requires visibility of the project and version.",
|
||||
security = @SecurityRequirement(name = "Session"),
|
||||
security = @SecurityRequirement(name = "HangarAuth"),
|
||||
tags = "Versions"
|
||||
)
|
||||
@ApiResponses({
|
||||
|
@ -94,7 +94,7 @@ async function deleteKey(key: ApiKey) {
|
||||
{{ i18n.t("apiKeys.createKey") }}
|
||||
</Button>
|
||||
</div>
|
||||
<InputGroup v-model="selectedPerms" :label="i18n.t('apiKeys.permissions')" class="w-full mt-2">
|
||||
<InputGroup v-model="selectedPerms" :label="i18n.t('apiKeys.permissions')" class="w-full mt-2 text-xl font-bold" full-width>
|
||||
<div class="grid autofix mt-2">
|
||||
<InputCheckbox v-for="perm in possiblePerms" :key="perm" v-model="selectedPerms" :label="perm" :value="perm" />
|
||||
</div>
|
||||
|
@ -37,6 +37,10 @@ onMounted(() => {
|
||||
if (req.url.startsWith("http://localhost:8080")) {
|
||||
req.url = req.url.replace("http://localhost:8080", "http://localhost:3333");
|
||||
}
|
||||
if (req.headers?.Authorization) {
|
||||
req.headers.Authorization = req.headers?.Authorization.replace("Bearer", "HangarAuth")
|
||||
}
|
||||
console.log(req)
|
||||
}
|
||||
return req;
|
||||
}
|
||||
@ -86,12 +90,10 @@ useHead(useSeo(i18n.t("apiDocs.title"), "API Docs for the Hangar REST API", rout
|
||||
}
|
||||
|
||||
.description h3 {
|
||||
padding-top: 1rem;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.description h4 {
|
||||
padding-top: 0.5rem;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user