Skip to content

Commit

Permalink
docs: migrate the api docs to be utoipa/redoc based (#1729)
Browse files Browse the repository at this point in the history
* migrate the api docs to be utoipa based

* further work

* made sure that the linking is working correctly
  • Loading branch information
CommanderStorm authored Dec 25, 2024
1 parent 395f7ef commit 93aac43
Show file tree
Hide file tree
Showing 35 changed files with 1,022 additions and 1,548 deletions.
8 changes: 0 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@ repos:
args:
- --fix=lf
- id: requirements-txt-fixer
# only api
- repo: local
hooks:
- id: openapi-format
name: openapi-format
entry: openapi-format ./data/output/openapi.yaml --output
language: system
files: "data/output/openapi.yaml"
# only server
- repo: local
hooks:
Expand Down
3 changes: 1 addition & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,5 @@ Please note we have a [Code of Conduct](CODE_OF_CONDUCT.md), please follow it in
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Update openapi.yaml with any breaking API-changes
4. You may merge the Pull Request in once you have the sign-off of one other developer, or if you
3. You may merge the Pull Request in once you have the sign-off of one other developer, or if you
do not have permission to do that, you may request the reviewer to merge it for you.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ You can consume our API Documentation in two ways:

- Head over to [our Website](https://nav.tum.de/api) and look at the interactive documentation
- We also describe our API in an [OpenAPI 3.0](https://de.wikipedia.org/wiki/OpenAPI) compliant file.
You can find it [here](openapi.yaml).
You can find it [here](https://nav.tum.de/api/openapi.json).
Using this Specification you can generate your own client to access the API in the language of your choice.
To do this head over to
the [Swagger Editor](https://editor.swagger.io/?url=https://raw.githubusercontent.com/TUM-Dev/navigatum/main/openapi.yaml)
the [Swagger Editor](https://editor.swagger.io/?url=https://nav.tum.de/api/openapi.json)
or other similar [OpenAPI tools](https://openapi.tools/).

> [!NOTE]
Expand Down
1 change: 0 additions & 1 deletion data/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ COPY translations.yaml translations.yaml
COPY output output

RUN python3 compile.py \
&& test -f "./output/openapi.yaml" \
&& test -f "./output/status_data.json" \
&& test -f "./output/status_data.parquet" \
&& test -f "./output/search_data.json" \
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ services:
- "traefik.enable=true"
- "traefik.http.routers.navigatum-server.entrypoints=webs"
- "traefik.http.routers.navigatum-server.tls.certresolver=leacme"
- "traefik.http.routers.navigatum-server.rule=Host(`nav.tum.de`) && (PathPrefix(`/api/locations/`) || PathPrefix(`/api/get/`) || PathPrefix(`/api/preview/`) || Path(`/api/search`) || PathPrefix(`/api/feedback/`) || Path(`/api/calendar`) || Path(`/api/status`)|| Path(`/api/metrics`) || PathPrefix(`/api/maps/indoor`))"
- "traefik.http.routers.navigatum-server.rule=Host(`nav.tum.de`) && Path(`/api`)"
- "traefik.http.services.navigatum-server.loadbalancer.server.port=3003"
networks:
- traefik_traefik
Expand Down
61 changes: 58 additions & 3 deletions server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ geozero = { version = "0.14.0", features = ["with-postgis-sqlx", "with-geo"], de
geo-types = { version = "0.7.13", default-features = false }
actix-middleware-etag = "0.4.2"

# docs
utoipa-actix-web = "0.1.2"
utoipa = { version = "5.2.0", features = ["yaml", "chrono", "actix_extras", "url"] }
utoipa-redoc = { version = "5.0.0", features = ["actix-web"] }
url = "2.5.4"

[dev-dependencies]
insta = { version = "1.39.0", features = ["json", "redactions", "yaml"] }
pretty_assertions = "1.4.1"
Expand Down
4 changes: 2 additions & 2 deletions server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ If you have made changes to the API, you need to update the API documentation.

There are two editors for the API documentation (both are imperfect):

- [Swagger Editor](https://editor.swagger.io/?url=https://raw.githubusercontent.com/TUM-Dev/navigatum/main/openapi.yaml)
- [Swagger Editor](https://editor.swagger.io/?url=https://nav.tum.de/api/openapi.json)
- [stoplight](https://stoplight.io/)

#### Testing
Expand All @@ -113,7 +113,7 @@ To do so, run the following commands against the API Server:
python -m venv venv
source venv/bin/activate
pip install schemathesis
st run --workers=auto --base-url=http://localhost:3003 --checks=all ../openapi.yaml
st run --workers=auto --base-url=http://localhost:3003 --checks=all https://nav.tum.de/api/openapi.json
```

Some fuzzing-goals may not be available for you locally, as they require prefix-routing (f.ex.`/cdn` to the CDN) and
Expand Down
58 changes: 50 additions & 8 deletions server/src/calendar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,23 @@ use actix_web::http::header::{CacheControl, CacheDirective};
mod connectum;
mod models;
pub mod refresh;

#[derive(Serialize, Deserialize, Clone, Debug)]
#[expect(
unused_imports,
reason = "has to be imported as otherwise utoipa generates incorrect code"
)]
use serde_json::json;
#[derive(Serialize, Deserialize, Clone, Debug, utoipa::IntoParams, utoipa::ToSchema)]
pub struct Arguments {
/// ids you want the calendars for
///
/// Limit of max. 10 ids is arbitraryly chosen, if you need this limit increased, please contact us
#[schema(max_items=10,min_items=1,example=json!(["5605.EG.011","5510.02.001","5606.EG.036","5304"]))]
ids: Vec<String>,
/// eg. 2039-01-19T03:14:07+1
/// The first allowed time the calendar would like to display
#[schema(examples("2039-01-19T03:14:07+01:00", "2042-01-07T00:00:00 UTC"))]
start_after: DateTime<Utc>,
/// eg. 2042-01-07T00:00:00 UTC
/// The last allowed time the calendar would like to display
#[schema(examples("2039-01-19T03:14:07+01:00", "2042-01-07T00:00:00 UTC"))]
end_before: DateTime<Utc>,
}

Expand All @@ -46,6 +56,22 @@ impl Arguments {
}
}

/// Retrieve Calendar Entries
///
/// Retrieves calendar entries for specific `ids` within the requested time span.
/// The time span is defined by the `start_after` and `end_before` query parameters.
/// Ensure to provide valid date-time formats for these parameters.
///
/// If successful, returns additional entries in the requested time span.
#[utoipa::path(
tags=["calendar"],
responses(
(status = 200, description = "**Entries of the calendar** in the requested time span", body = HashMap<String, LocationEvents>, content_type = "application/json"),
(status = 400, description= "**Bad Request.** Not all fields in the body are present as defined above", body = String, example = "Too many ids to query. We suspect that users don't need this. If you need this limit increased, please send us a message"),
(status = 404, description = "**Not found.** The requested location does not have a calendar", body = String, content_type = "text/plain", example = "Not found"),
(status = 503, description = "**Not Ready.** please retry later", body = String, content_type = "text/plain", example = "Waiting for first sync with TUMonline"),
)
)]
#[post("/api/calendar")]
pub async fn calendar_handler(
web::Json(args): web::Json<Arguments>,
Expand Down Expand Up @@ -113,7 +139,14 @@ async fn get_locations(
pool: &PgPool,
ids: &[String],
) -> Result<LimitedVec<CalendarLocation>, HttpResponse> {
match sqlx::query_as!(CalendarLocation, "SELECT key,name,last_calendar_scrape_at,calendar_url,type,type_common_name FROM de WHERE key = ANY($1::text[])", ids).fetch_all(pool).await {
match sqlx::query_as!(
CalendarLocation,
"SELECT key,name,last_calendar_scrape_at,calendar_url,type,type_common_name FROM de WHERE key = ANY($1::text[])",
ids
)
.fetch_all(pool)
.await
{
Err(e) => {
error!("could not refetch due to {e:?}");
Err(HttpResponse::InternalServerError()
Expand All @@ -133,10 +166,17 @@ async fn get_from_db(
) -> anyhow::Result<LimitedHashMap<String, LocationEvents>> {
let mut located_events: HashMap<String, LocationEvents> = HashMap::new();
for location in locations {
let events = sqlx::query_as!(Event, r#"SELECT id,room_code,start_at,end_at,title_de,title_en,stp_type,entry_type,detailed_entry_type
let events = sqlx::query_as!(
Event,
r#"SELECT id,room_code,start_at,end_at,title_de,title_en,stp_type,entry_type,detailed_entry_type
FROM calendar
WHERE room_code = $1 AND start_at >= $2 AND end_at <= $3"#,
location.key, start_after, end_before).fetch_all(pool).await?;
location.key,
start_after,
end_before
)
.fetch_all(pool)
.await?;
located_events.insert(
location.key.clone(),
LocationEvents {
Expand Down Expand Up @@ -266,7 +306,9 @@ mod db_tests {
let (locations, events) = sample_data();
for (key, data) in locations {
for lang in ["de", "en"] {
let query = format!("INSERT INTO {lang}(key,data,last_calendar_scrape_at) VALUES ('{key}','{data}','{now_rfc3339}')");
let query = format!(
"INSERT INTO {lang}(key,data,last_calendar_scrape_at) VALUES ('{key}','{data}','{now_rfc3339}')"
);
sqlx::query(&query).execute(&mut *tx).await.unwrap();
}
}
Expand Down
Loading

0 comments on commit 93aac43

Please sign in to comment.