Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/apps/api/ViGet/ViGetEndpoints.cs
blob: 00f6dc4f31cb31c21e3bebd885fc09f6fb60db8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1

namespace Progmasoft.Progmaweb.Api.ViGet;

/// <summary>The read endpoints of the Tazel plugin catalog.</summary>
/// <remarks>
/// <para>
/// The pages of ViGet read the catalog from these endpoints on the loopback address of the API; Nginx does not pass
/// <c>/api/</c> of the ViGet host on. What the public reaches is the index of a plugin, which Tazel reads at
/// <c>https://viget.progmasoft.com/dslplugins/tazel/&lt;Publisher&gt;/&lt;Name&gt;/index.json</c>: Nginx passes
/// that address to <c>/api/v1/viget/dslplugins/tazel/&lt;Publisher&gt;/&lt;Name&gt;/index.json</c>, and serves the
/// JARs from the files of ViGet itself.
/// </para>
/// </remarks>
internal static class ViGetEndpoints
{
    /// <summary>Maps the endpoints.</summary>
    /// <param name="app">The routes of the API.</param>
    public static void MapViGetEndpoints(this IEndpointRouteBuilder app)
    {
        RouteGroupBuilder tazel = app.MapGroup("/api/v1/viget/dslplugins/tazel");

        tazel.MapGet("/", async (ITazelPluginCatalog catalog, CancellationToken cancellationToken) =>
        {
            IReadOnlyList<TazelPlugin> plugins = await catalog.ListAsync(cancellationToken);
            return Results.Ok(new
            {
                plugins = plugins.Select(plugin => new
                {
                    plugin.Publisher,
                    plugin.Name,
                    plugin.Description,
                    plugin.License,
                    latestVersion = plugin.Latest.Version,
                    updatedAt = plugin.Versions[0].PublishedAt
                })
            });
        });

        tazel.MapGet("/{publisher}/{name}", async (
            string publisher,
            string name,
            ITazelPluginCatalog catalog,
            CancellationToken cancellationToken) =>
        {
            TazelPlugin? plugin = await FindAsync(catalog, publisher, name, cancellationToken);
            return plugin is null
                ? Results.NotFound()
                : Results.Ok(new
                {
                    plugin.Publisher,
                    plugin.Name,
                    plugin.Description,
                    plugin.Authors,
                    plugin.License,
                    plugin.Homepage,
                    plugin.Repository,
                    latestVersion = plugin.Latest.Version,
                    versions = plugin.Versions.Select(version => new
                    {
                        version.Version,
                        version.Sha256,
                        version.ApiVersion,
                        version.Size,
                        version.PublishedAt,
                        version.Yanked,
                        path = $"/dslplugins/tazel/{plugin.Publisher}/{plugin.Name}/{version.Version}/" +
                               $"{plugin.Name}-{version.Version}.jar"
                    })
                });
        });

        // The index Tazel reads to choose a version and check the JAR it downloads.
        tazel.MapGet("/{publisher}/{name}/index.json", async (
            string publisher,
            string name,
            ITazelPluginCatalog catalog,
            CancellationToken cancellationToken) =>
        {
            TazelPlugin? plugin = await FindAsync(catalog, publisher, name, cancellationToken);
            return plugin is null
                ? Results.NotFound()
                : Results.Ok(new
                {
                    plugin.Publisher,
                    plugin.Name,
                    versions = plugin.Versions.Select(version => new
                    {
                        version.Version,
                        version.Sha256,
                        version.ApiVersion,
                        version.Yanked
                    })
                });
        });
    }

    /// <summary>A plugin of the catalog, or <see langword="null"/> when a route value is not an identifier.</summary>
    /// <param name="catalog">The catalog.</param>
    /// <param name="publisher">The publisher from the route.</param>
    /// <param name="name">The name from the route.</param>
    /// <param name="cancellationToken">Cancels the operation.</param>
    /// <returns>The plugin, if there is one.</returns>
    private static ValueTask<TazelPlugin?> FindAsync(
        ITazelPluginCatalog catalog,
        string publisher,
        string name,
        CancellationToken cancellationToken) =>
        TazelPluginDescriptor.IsIdentifier(publisher) && TazelPluginDescriptor.IsIdentifier(name)
            ? catalog.FindAsync(publisher, name, cancellationToken)
            : ValueTask.FromResult<TazelPlugin?>(null);
}