From 9d6418ed1bb6b573ca43e67617171277e21587b8 Mon Sep 17 00:00:00 2001
From: Marc Wrobel
Date: Fri, 16 Dec 2022 22:34:24 +0100
Subject: [PATCH 1/3] First version
---
...reate-json-files.rb => generate-api-v0.rb} | 0
_plugins/generate-api-v1.rb | 89 +++++++++++++++++++
2 files changed, 89 insertions(+)
rename _plugins/{create-json-files.rb => generate-api-v0.rb} (100%)
create mode 100755 _plugins/generate-api-v1.rb
diff --git a/_plugins/create-json-files.rb b/_plugins/generate-api-v0.rb
similarity index 100%
rename from _plugins/create-json-files.rb
rename to _plugins/generate-api-v0.rb
diff --git a/_plugins/generate-api-v1.rb b/_plugins/generate-api-v1.rb
new file mode 100755
index 00000000000..2f93923af10
--- /dev/null
+++ b/_plugins/generate-api-v1.rb
@@ -0,0 +1,89 @@
+#!/usr/bin/env ruby
+
+# This script creates API files for version 1 of the endoflife.date API.
+#
+# There are three kind of generated files :
+# - all.json: contains the list of all the product names.
+# - .json: contains a given product data ()including releases data).
+# - /.json: contains a given product release data.
+
+require 'fileutils'
+require 'json'
+require 'yaml'
+require 'date'
+
+module ApiV1
+
+ # This API path
+ DIR = 'api/v1'
+
+ # Returns the path of a file inside the API namespace.
+ def self.file(name, *args)
+ File.join(DIR, name, *args)
+ end
+
+ # Holds information about a product.
+ Product = Class.new do
+ attr_accessor :data
+
+ # Initializes the product with the given product's markdown file.
+ # The markdown file is expected to contain a YAML front-matter with the appropriate properties.
+ #
+ # Copying the data makes it easier to process it.
+ def initialize(data)
+ @data = Hash.new
+ # The product name is derived from the product's permalink (ex. /debian => debian).
+ @data["name"] = data['permalink'][1..data['permalink'].length]
+ @data["title"] = data['title']
+ @data["category"] = data['category']
+ @data["iconSlug"] = data['iconSlug']
+ @data["permalink"] = data['permalink']
+ @data["versionCommand"] = data['versionCommand']
+ @data["auto"] = data.has_key? 'auto'
+ @data["releasePolicyLink"] = data['releasePolicyLink']
+ @data["releases"] = data['releases'].map do |release|
+ release_data = Hash.new
+ release_data["name"] = release['releaseCycle']
+ release_data["codename"] = release['codename']
+ release_data["releaseDate"] = release['releaseDate']
+ release_data["support"] = release['support']
+ release_data["eol"] = release['eol']
+ release_data["discontinued"] = release['discontinued']
+ release_data["lts"] = release['lts'] || false # lts is optional, make sure it always has a value
+ release_data["latest"] = release['latest']
+ release_data["latestReleaseDate"] = release['latestReleaseDate']
+ release_data
+ end
+ end
+
+ def name
+ data["name"]
+ end
+ end
+end
+
+product_names = []
+FileUtils.mkdir_p(ApiV1::file('.'))
+
+Dir['products/*.md'].each do |file|
+ # Load and prepare data
+ raw_data = YAML.safe_load(File.open(file), permitted_classes: [Date])
+ product = ApiV1::Product.new(raw_data)
+ product_names.append(product.name)
+
+ # Write /.json
+ product_file = ApiV1::file("#{product.name}.json")
+ File.open(product_file, 'w') { |f| f.puts product.data.to_json }
+
+ # Write all //.json
+ FileUtils.mkdir_p(ApiV1::file(product.name))
+ product.data["releases"].each do |release|
+ # Any / characters in the name are replaced with - to avoid file errors.
+ release_file = ApiV1::file(product.name, "#{release['name'].to_s.tr('/', '-')}.json")
+ File.open(release_file, 'w') { |f| f.puts release.to_json }
+ end
+end
+
+# Write /all.json
+all_products_file = ApiV1::file('all.json')
+File.open(all_products_file, 'w') { |f| f.puts product_names.sort.to_json }
From d164e722c5d8ca2c21818365f840e4769fdf2830 Mon Sep 17 00:00:00 2001
From: Marc Wrobel
Date: Sat, 17 Dec 2022 13:23:05 +0100
Subject: [PATCH 2/3] Add API v1 (#2595, #2425, #2331, #2066, #2062, #1762,
#759, #394, #2530)
This is a major rework of the API with a lot of breaking changes. See CHANGELOG_API.md for more information.
Note that we thought of disabling API generation in development (using JEKYLL_ENV like the Jekyll Feed plugin - see https://github.com/jekyll/jekyll-feed/blob/master/lib/jekyll-feed/generator.rb#L145), but it was finally reverted. It does not work well with Netlify preview, and generate production URL (i.e. https://endoflife.date URLs) in development which makes it difficult to use.
---
CHANGELOG_API.md | 96 ++++++
HACKING.md | 8 +-
README.md | 7 +-
_config.yml | 2 +-
_headers | 4 +
_layouts/json.json | 1 +
_layouts/product.html | 4 +-
_layouts/swagger-ui.html | 31 ++
_plugins/generate-api-v1.rb | 342 +++++++++++++++----
_plugins/product-data-enricher.rb | 23 +-
_redirects | 22 +-
api_v1/openapi.yml | 535 ++++++++++++++++++++++++++++++
api_v1/swagger-ui.md | 6 +
assets/404.json | 3 -
humans.txt | 2 +-
index.md | 4 +-
16 files changed, 996 insertions(+), 94 deletions(-)
create mode 100644 CHANGELOG_API.md
create mode 100644 _layouts/json.json
create mode 100644 _layouts/swagger-ui.html
create mode 100644 api_v1/openapi.yml
create mode 100644 api_v1/swagger-ui.md
delete mode 100644 assets/404.json
diff --git a/CHANGELOG_API.md b/CHANGELOG_API.md
new file mode 100644
index 00000000000..3bc9e66b4bd
--- /dev/null
+++ b/CHANGELOG_API.md
@@ -0,0 +1,96 @@
+## API v1.0.0
+
+### Summary
+
+API v1 is a major rework of the API v0 with a lot of breaking changes. Compared to the API v0, API
+v1:
+
+- feels more _Restful_ (#2431),
+- expose almost all product's data (#394, #759, #2062, #2595),
+- expose new metadata such as `schema version` (#2331), `total` (for lists), `generated_at` or
+ `last modified` date,
+- is easier to consume thanks to:
+ - new computed fields such as `is_maintained`,
+ - the replacement of fields that were using union types with two separate single-type fields:
+ - `lts` -> `isLts` and `ltsFrom`,
+ - `support` -> `isActiveSupportOver` and `activeSupportUntil`,
+ - `eol` -> `isEol` and `eolFrom`,
+ - `discontinued` -> `isDiscontinued` and `discontinuedFrom`,
+ - `extendedSupport` -> `isExtendedSupportOver` and `extendedSupportUntil`.
+- provide new endpoints (#2078, #2160, #2530)
+- is versioned using the `api/v1` prefix (#2066), making it easier to implement
+ non-backward-compatible changes in the future,
+- is documented using [swagger-ui](https://swagger.io/tools/swagger-ui/) instead of [Stoplight
+ Elements WebComponent](https://github.com/stoplightio/elements/blob/main/docs/getting-started/elements/html.md)
+ (#905),
+- but reverts #2425 due to incompatibilities in redirect rules.
+
+The API v1 is now generated using a Jekyll Generator (see https://jekyllrb.com/docs/plugins/generators/)
+instead of a custom script.
+
+Note that the API v0 is still generated to give time to users to migrate to API v1. It will be
+decommissioned at least one year after the API v1 release date.
+
+API v1 documentation can be seen on .
+The old API v0 documentation can still be seen on .
+
+### Changes in the "All products" endpoint
+
+- Path has been changed from `api/all.json` to `api/v1/products/`
+- Response has been changed from a simple array of strings to a JSON document.
+ This made it possible to include additional metadata, such as the schema version and the number of
+ products.
+- Response items has been changed from a simple string (the product name) to a JSON document (#2062).
+ This made it possible to include additional information about the product, such as its category
+ and tags.
+- See for a detailed description of the
+ response.
+
+### Changes in the "Product" endpoint
+
+- Path has been changed from `api/.json` to `api/v1/products//`.
+- Response has been changed from a simple array of versions to a JSON document.
+ This made it possible to include :
+ - additional metadata, such as the schema version and the last modified date,
+ - product-level information, such as the product label or category (#2062).
+- Cycles data now always contain all the release cycles properties, even if they are null
+ (example: `discontinued`, `latest`, `latestReleaseDate`, `support`...).
+- See for a detailed
+ description of the response.
+
+### Changes in the "Cycle" endpoint
+
+- Path has been changed from `api//.json` to `api/v1/products//cycles//`.
+- Response has been changed to make it possible to include additional metadata, such as the schema
+ version and the last modified date,
+- Cycles data now always contain all the release cycles properties, even if they are null
+ (example: `discontinued`, `latest`, `latestReleaseDate`, `support`...).
+- A special `/api/v1/products//cycles/latest/` cycle, containing the same data as the
+ latest cycle, has been added (#2078).
+- See for a
+ detailed description of the response.
+
+### Changes in 404 error responses
+
+404 error JSON responses are not returned anymore. #2425 has been reverted because it conflicted
+with the rule that rewrites the paths to add `/index.json` to all requests, which is also a global
+rule and [takes precedence](https://docs.netlify.com/routing/redirects/#rule-processing-order).
+
+### New endpoints
+
+- `/api/v1/categories/`: Get a list of all categories.
+- `/api/v1/categories/`: Get a list of all products within the given category.
+- `/api/v1/tags/`: Get a list of all tags.
+- `/api/v1/tags/`: Get a list of all products having the given tag.
+- `/api/v1/products/full/`: Get a list of all products with all their details (including cycles).
+ This endpoint provides a dump of nearly all the endoflife.date data.
+
+
+
+## API v0
+
+On 2023-03-02 the v0 endpoints were:
+
+- "All products" (`/api/all.json`) : Get a list of all product names.
+- "Product" (`/api/{product}.json`) : Get all release cycles details for a given product.
+- "Cycle" (`/api/{product}/{cycle}.json`) : Get details for a single release cycle of a given product.
diff --git a/HACKING.md b/HACKING.md
index 09428e87105..cc8cb0394b2 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -103,7 +103,13 @@ The API is just JSON files generated in the `api` directory by `_plugins/create-
### API Documentation
-The API Documentation is available at and is generated from an OpenAPI Specification file located at `assets/openapi.yml`. The documentation is rendered [Stoplight Elements](https://meta.stoplight.io/docs/elements/ZG9jOjMyNjU4OTY0-introduction-to-elements).
+The current API v1 documentation is available at and is
+generated from an OpenAPI Specification file located at `api_v1/openapi.yml`. The documentation is
+rendered by [Swagger UI](https://swagger.io/tools/swagger-ui/).
+
+The old API v0 documentation is available at and is
+generated from an OpenAPI Specification file located at `assets/openapi.yml`. The documentation is
+rendered by [Stoplight Elements](https://meta.stoplight.io/docs/elements/ZG9jOjMyNjU4OTY0-introduction-to-elements).
## Contributing Workflow
diff --git a/README.md b/README.md
index 9c34df77bde..97a7cf2fcdd 100644
--- a/README.md
+++ b/README.md
@@ -26,9 +26,8 @@ While participating in the project, you must abide by its [Code of Conduct](CODE
## API
-An API is available for integration with CI platforms.
-API documentation is available at https://endoflife.date/docs/api.
-The API is currently in Alpha, and breaking changes can happen.
+An API is available for integration with CI platforms. API documentation is available at https://endoflife.date/docs/api/v1/.
+The API is currently in Beta, and breaking changes can happen.
## License
@@ -46,6 +45,8 @@ endoflife.date is relying on various amazing software and components :
- [Just the Docs](https://github.com/just-the-docs/just-the-docs), a documentation theme for Jekyll.
- [Stoplight Elements](https://stoplight.io/open-source/elements), a collection of UI components for
displaying beautiful developer documentation from any OpenAPI document.
+- [Swagger UI](https://swagger.io/tools/swagger-ui/), a documentation generator for OpenAPI
+ Specification.
- [Simple Icons](https://simpleicons.org/), free SVG icons for popular brands.
- Our icon is derived from [Hourglass icon (orange)](https://commons.wikimedia.org/wiki/File:Hourglass_icon_%28orange%29.svg)
by David Abián and Serhio Magpie on the English Wikipedia. Remixed under the CC-BY-SA-4.0 license.
diff --git a/_config.yml b/_config.yml
index 708b02b4dd7..de8f4cf8ade 100644
--- a/_config.yml
+++ b/_config.yml
@@ -40,7 +40,7 @@ aux_links:
Source:
- https://github.com/endoflife-date/endoflife.date
API:
- - /docs/api
+ - /docs/api/v1/
"Release Data":
- https://github.com/endoflife-date/release-data/
diff --git a/_headers b/_headers
index 7bb1e154e5e..1c17bb4ade4 100644
--- a/_headers
+++ b/_headers
@@ -56,6 +56,10 @@ layout: null
Content-Security-Policy: default-src 'none'; manifest-src 'self'; connect-src 'self'; script-src 'self'; style-src 'self'; img-src {{ defaultCspImgSrc }} {{ releaseImageSrc }}
Link: /api{{page.permalink}}.json; rel=alternate;type=application/json
Link: /calendar{{page.permalink}}.ics; rel=alternate;type=text/calendar
+ {% elsif page.permalink contains '/docs/api/v' %}
+ {%- comment %}Used contains to match all API version (startswith does not exist){% endcomment %}
+ # unsafe-inline and data: should not be an issue for a static site
+ Content-Security-Policy: default-src 'none'; manifest-src 'self'; connect-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com/; style-src 'self' https://unpkg.com/; img-src 'self' data:
{% elsif page.permalink == '/docs/api' %}
Content-Security-Policy: default-src 'none'; manifest-src 'self'; connect-src 'self'; script-src 'self' https://unpkg.com/@stoplight/elements/web-components.min.js; style-src 'self' https://unpkg.com/@stoplight/elements/ 'unsafe-inline'
{% else %}
diff --git a/_layouts/json.json b/_layouts/json.json
new file mode 100644
index 00000000000..8c98299c885
--- /dev/null
+++ b/_layouts/json.json
@@ -0,0 +1 @@
+{{ page.data | jsonify }}
diff --git a/_layouts/product.html b/_layouts/product.html
index 9e1b781e051..21239be4167 100644
--- a/_layouts/product.html
+++ b/_layouts/product.html
@@ -198,7 +198,7 @@