From 4959d6e16fd9501b6c75682b0376a1d6c12df1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Knuchel?= Date: Sun, 7 Jan 2024 14:42:42 +0100 Subject: [PATCH] Improve swagger --- .../lib/azimutt/accounts/user_auth_token.ex | 3 + .../controllers/api/gallery_controller.ex | 39 --- .../controllers/api/metadata_controller.ex | 38 +-- .../api/organization_controller.ex | 35 +++ .../controllers/api/project_controller.ex | 65 ++-- .../controllers/api/source_controller.ex | 289 ++++++++++++++++++ .../controllers/api/user_controller.ex | 34 +++ .../lib/azimutt_web/controllers/user_auth.ex | 5 +- backend/lib/azimutt_web/router.ex | 2 +- .../lib/azimutt_web/utils/project_schema.ex | 2 +- .../lib/azimutt_web/utils/swagger_common.ex | 22 +- 11 files changed, 430 insertions(+), 104 deletions(-) diff --git a/backend/lib/azimutt/accounts/user_auth_token.ex b/backend/lib/azimutt/accounts/user_auth_token.ex index 0d562be26..a111acdab 100644 --- a/backend/lib/azimutt/accounts/user_auth_token.ex +++ b/backend/lib/azimutt/accounts/user_auth_token.ex @@ -4,6 +4,7 @@ defmodule Azimutt.Accounts.UserAuthToken do use Azimutt.Schema import Ecto.Changeset alias Azimutt.Accounts.User + alias Azimutt.Utils.Uuid schema "user_auth_tokens" do belongs_to :user, User @@ -32,4 +33,6 @@ defmodule Azimutt.Accounts.UserAuthToken do token |> cast(%{deleted_at: now}, [:deleted_at]) end + + def is_valid?(value), do: Uuid.is_valid?(value) end diff --git a/backend/lib/azimutt_web/controllers/api/gallery_controller.ex b/backend/lib/azimutt_web/controllers/api/gallery_controller.ex index 0edefb934..eea739531 100644 --- a/backend/lib/azimutt_web/controllers/api/gallery_controller.ex +++ b/backend/lib/azimutt_web/controllers/api/gallery_controller.ex @@ -1,48 +1,9 @@ defmodule AzimuttWeb.Api.GalleryController do use AzimuttWeb, :controller - use PhoenixSwagger alias Azimutt.Gallery action_fallback AzimuttWeb.Api.FallbackController - swagger_path :index do - get("/gallery") - summary("List sample projects") - description("List sample projects") - produces("application/json") - tag("Samples") - - response(200, "OK", Schema.ref(:Samples)) - response(400, "Client Error") - end - def index(conn, _params) do conn |> render("index.json", samples: Gallery.list_samples()) end - - def swagger_definitions do - %{ - Sample: - swagger_schema do - title("Sample") - description("A Sample project") - - properties do - slug(:string, "Sample slug", required: true) - color(:string, "Sample color", required: true) - icon(:string, "Sample icon", required: true) - name(:string, "Project name", required: true) - description(:string, "Project description", required: true) - project_id(:string, "Project id", required: true) - nb_tables(:integer, "Nb project tables", required: true) - end - end, - Samples: - swagger_schema do - title("Samples") - description("A collection of Samples") - type(:array) - items(Schema.ref(:Sample)) - end - } - end end diff --git a/backend/lib/azimutt_web/controllers/api/metadata_controller.ex b/backend/lib/azimutt_web/controllers/api/metadata_controller.ex index 416bd3af3..782f98359 100644 --- a/backend/lib/azimutt_web/controllers/api/metadata_controller.ex +++ b/backend/lib/azimutt_web/controllers/api/metadata_controller.ex @@ -14,12 +14,11 @@ defmodule AzimuttWeb.Api.MetadataController do tag("Metadata") summary("Get project metadata") description("Get all metadata for the project, ie all notes and tags for all tables and columns.") - produces("application/json") get("#{SwaggerCommon.project_path()}/metadata") SwaggerCommon.authorization() SwaggerCommon.project_params() - response(200, "OK", Schema.ref(:ProjectMetadata)) + response(200, "OK", Schema.ref(:Metadata)) response(400, "Client Error") end @@ -33,16 +32,15 @@ defmodule AzimuttWeb.Api.MetadataController do tag("Metadata") summary("Update project metadata") description("Set the whole project metadata at once. Fetch it, update it then update it. Beware to not override changes made by others.") - produces("application/json") put("#{SwaggerCommon.project_path()}/metadata") SwaggerCommon.authorization() SwaggerCommon.project_params() parameters do - payload(:body, :object, "Project Metadata", required: true, schema: Schema.ref(:ProjectMetadata)) + payload(:body, :object, "Project Metadata", required: true, schema: Schema.ref(:Metadata)) end - response(200, "OK", Schema.ref(:ProjectMetadata)) + response(200, "OK", Schema.ref(:Metadata)) response(400, "Client Error") end @@ -59,7 +57,6 @@ defmodule AzimuttWeb.Api.MetadataController do tag("Metadata") summary("Get table metadata") description("Get all metadata for the table, notes and tags. You can include columns metadata too with the `expand` query param.") - produces("application/json") get("#{SwaggerCommon.project_path()}/tables/{table_id}/metadata") SwaggerCommon.authorization() SwaggerCommon.project_params() @@ -69,7 +66,7 @@ defmodule AzimuttWeb.Api.MetadataController do expand(:query, :array, "Expand columns metadata", collectionFormat: "csv", items: %{type: "string", enum: ["columns"]}) end - response(200, "OK", Schema.ref(:TableMetadata)) + response(200, "OK", Schema.ref(:MetadataTable)) response(400, "Client Error") end @@ -83,17 +80,16 @@ defmodule AzimuttWeb.Api.MetadataController do tag("Metadata") summary("Update table metadata") description("Set table metadata. If you include columns, they will be replaced, otherwise they will stay the same.") - produces("application/json") put("#{SwaggerCommon.project_path()}/tables/{table_id}/metadata") SwaggerCommon.authorization() SwaggerCommon.project_params() parameters do table_id(:path, :string, "Id of the table (ex: public.users)", required: true) - payload(:body, :object, "Table Metadata", required: true, schema: Schema.ref(:TableMetadata)) + payload(:body, :object, "Table Metadata", required: true, schema: Schema.ref(:MetadataTable)) end - response(200, "OK", Schema.ref(:TableMetadata)) + response(200, "OK", Schema.ref(:MetadataTable)) response(400, "Client Error") end @@ -117,7 +113,6 @@ defmodule AzimuttWeb.Api.MetadataController do tag("Metadata") summary("Get column metadata") description("Get all metadata for the column, notes and tags. For nested columns, use the column path (ex: details:address:street).") - produces("application/json") get("#{SwaggerCommon.project_path()}/tables/{table_id}/columns/{column_path}/metadata") SwaggerCommon.authorization() SwaggerCommon.project_params() @@ -127,7 +122,7 @@ defmodule AzimuttWeb.Api.MetadataController do column_path(:path, :string, "Path of the column (ex: id, name or details:location)", required: true) end - response(200, "OK", Schema.ref(:ColumnMetadata)) + response(200, "OK", Schema.ref(:MetadataColumn)) response(400, "Client Error") end @@ -141,7 +136,6 @@ defmodule AzimuttWeb.Api.MetadataController do tag("Metadata") summary("Update column metadata") description("Set column metadata. For nested columns, use the column path (ex: details:address:street).") - produces("application/json") put("#{SwaggerCommon.project_path()}/tables/{table_id}/columns/{column_path}/metadata") SwaggerCommon.authorization() SwaggerCommon.project_params() @@ -149,10 +143,10 @@ defmodule AzimuttWeb.Api.MetadataController do parameters do table_id(:path, :string, "Id of the table (ex: public.users)", required: true) column_path(:path, :string, "Path of the column (ex: id, name or details:location)", required: true) - payload(:body, :object, "Column Metadata", required: true, schema: Schema.ref(:ColumnMetadata)) + payload(:body, :object, "Column Metadata", required: true, schema: Schema.ref(:MetadataColumn)) end - response(200, "OK", Schema.ref(:ColumnMetadata)) + response(200, "OK", Schema.ref(:MetadataColumn)) response(400, "Client Error") end @@ -167,12 +161,10 @@ defmodule AzimuttWeb.Api.MetadataController do def swagger_definitions do %{ - ProjectMetadata: + Metadata: swagger_schema do - title("ProjectMetadata") description("All Metadata of the project") - type(:object) - additional_properties(Schema.ref(:TableMetadata)) + additional_properties(Schema.ref(:MetadataTable)) example(%{ "public.users": %{ @@ -194,15 +186,14 @@ defmodule AzimuttWeb.Api.MetadataController do } }) end, - TableMetadata: + MetadataTable: swagger_schema do - title("TableMetadata") description("The Metadata used to document tables") properties do notes(:string, "Markdown text to document the table", example: "*Table* notes") tags(:array, "Tags to categorize the table", items: %{type: :string}, example: ["table-tag"]) - columns(:object, "Columns metadata", additionalProperties: Schema.ref(:ColumnMetadata)) + columns(:object, "Columns metadata", additionalProperties: Schema.ref(:MetadataColumn)) end example(%{ @@ -220,9 +211,8 @@ defmodule AzimuttWeb.Api.MetadataController do } }) end, - ColumnMetadata: + MetadataColumn: swagger_schema do - title("ColumnMetadata") description("The Metadata used to document columns") properties do diff --git a/backend/lib/azimutt_web/controllers/api/organization_controller.ex b/backend/lib/azimutt_web/controllers/api/organization_controller.ex index fdeb7adf0..cc8ed83cb 100644 --- a/backend/lib/azimutt_web/controllers/api/organization_controller.ex +++ b/backend/lib/azimutt_web/controllers/api/organization_controller.ex @@ -7,8 +7,20 @@ defmodule AzimuttWeb.Api.OrganizationController do alias Azimutt.Tracking alias Azimutt.Utils.Result alias AzimuttWeb.Utils.CtxParams + alias AzimuttWeb.Utils.SwaggerCommon action_fallback AzimuttWeb.Api.FallbackController + swagger_path :index do + tag("Organizations") + summary("List user organizations") + description("Get the list of all organizations the user is part of.") + get("/organizations") + SwaggerCommon.authorization() + + response(200, "OK", Schema.ref(:Organizations)) + response(400, "Client Error") + end + def index(conn, params) do current_user = conn.assigns.current_user ctx = CtxParams.from_params(params) @@ -40,4 +52,27 @@ defmodule AzimuttWeb.Api.OrganizationController do conn |> render("table_colors.json", tweet: tweet, errors: errors) end end + + def swagger_definitions do + %{ + Organization: + swagger_schema do + description("An Organization in Azimutt") + + properties do + id(:string, "Unique identifier", required: true, example: "0ed17b9f-cb2c-403a-a148-03215b73deb4") + slug(:string, "Organization slug", required: true, example: "azimutt") + name(:string, "Organization name", required: true, example: "Azimutt") + logo(:string, "Organization logo", example: "https://azimutt.app/images/logo_icon_dark.svg") + description(:string, "Organization description", example: "The best database explorer for any database!") + end + end, + Organizations: + swagger_schema do + description("A collection of Organizations") + type(:array) + items(Schema.ref(:Organization)) + end + } + end end diff --git a/backend/lib/azimutt_web/controllers/api/project_controller.ex b/backend/lib/azimutt_web/controllers/api/project_controller.ex index 95a2aadf6..39f3b1166 100644 --- a/backend/lib/azimutt_web/controllers/api/project_controller.ex +++ b/backend/lib/azimutt_web/controllers/api/project_controller.ex @@ -6,26 +6,40 @@ defmodule AzimuttWeb.Api.ProjectController do alias Azimutt.Projects alias Azimutt.Projects.Project alias AzimuttWeb.Utils.CtxParams + alias AzimuttWeb.Utils.SwaggerCommon action_fallback AzimuttWeb.Api.FallbackController swagger_path :index do - get("/projects") - summary("Query for projects") - description("Query for projects. This operation supports with paging and filtering") - produces("application/json") tag("Projects") + summary("List organization projects") + description("Get the list of all projects in the organization.") + get("#{SwaggerCommon.organization_path()}/projects") + SwaggerCommon.authorization() + SwaggerCommon.organization_params() response(200, "OK", Schema.ref(:Projects)) response(400, "Client Error") end - def index(conn, %{"organization_id" => organization_id}) do + def index(conn, %{"organization_organization_id" => organization_id}) do current_user = conn.assigns.current_user with {:ok, %Organization{} = organization} <- Organizations.get_organization(organization_id, current_user), do: conn |> render("index.json", projects: organization.projects, current_user: current_user) end + swagger_path :show do + tag("Projects") + summary("Get a project") + description("Fetch the required project.") + get(SwaggerCommon.project_path()) + SwaggerCommon.authorization() + SwaggerCommon.project_params() + + response(200, "OK", Schema.ref(:Project)) + response(400, "Client Error") + end + def show(conn, %{"organization_id" => _organization_id, "project_id" => project_id} = params) do now = DateTime.utc_now() maybe_current_user = conn.assigns.current_user @@ -35,17 +49,6 @@ defmodule AzimuttWeb.Api.ProjectController do do: conn |> render("show.json", project: project, maybe_current_user: maybe_current_user, ctx: ctx) end - swagger_path :create do - post("/organizations/:organization_id/projects") - summary("Create a project") - description("TODO") - produces("application/json") - tag("Projects") - - response(201, "Created") - response(400, "Client Error") - end - def create(conn, %{"organization_organization_id" => organization_id} = params) do current_user = conn.assigns.current_user ctx = CtxParams.from_params(params) @@ -81,24 +84,32 @@ defmodule AzimuttWeb.Api.ProjectController do %{ Project: swagger_schema do - title("Project") description("An Azimutt project") properties do - name(:string, "Project name", required: true) - id(:string, "Unique identifier", required: true) - slug(:string, "Project slug") + id(:string, "Unique identifier", required: true, example: "a2cf8a87-0316-40eb-98ce-72659dae9420") + slug(:string, "Project slug", required: true, example: "azimutt-dev") + name(:string, "Project name", required: true, example: "Azimutt dev") + description(:string, "Project description", example: "Explore the Azimutt local database") + encoding_version(:integer, "Project storage version", required: true, example: 2) + storage_kind(:string, "Project storage kind", enum: ["local", "remote"], required: true, example: "local") + visibility(:string, "If the project is publicly visible", enum: ["none", "read", "write"], required: true, example: "read") + nb_sources(:integer, "Number of sources in the project", required: true, example: 2) + nb_tables(:integer, "Number of tables in the project", required: true, example: 563) + nb_columns(:integer, "Number of columns in the project", required: true, example: 13_945) + nb_relations(:integer, "Number of relations in the project", required: true, example: 2524) + nb_types(:integer, "Number of custom types in the project", required: true, example: 4) + nb_comments(:integer, "Number of SQL comments in the project", required: true, example: 892) + nb_layouts(:integer, "Number of layouts in the project", required: true, example: 32) + nb_notes(:integer, "Number of notes in the project", required: true, example: 3264) + nb_memos(:integer, "Number of memos in the project", required: true, example: 183) + created_at(:string, "When the project was created", format: "date-time", required: true, example: "2023-05-12T06:56:41.467400Z") + updated_at(:string, "The last time the project was updated", format: "date-time", required: true, example: "2023-06-28T14:43:12.345289Z") + archived_at(:string, "When the project was archived", format: "date-time", example: "2024-01-07T07:55:12.174780Z") end - - example(%{ - name: "Project name", - id: "123", - slug: "project-name" - }) end, Projects: swagger_schema do - title("Projects") description("A collection of Projects") type(:array) items(Schema.ref(:Project)) diff --git a/backend/lib/azimutt_web/controllers/api/source_controller.ex b/backend/lib/azimutt_web/controllers/api/source_controller.ex index 87787e95f..7f6de1602 100644 --- a/backend/lib/azimutt_web/controllers/api/source_controller.ex +++ b/backend/lib/azimutt_web/controllers/api/source_controller.ex @@ -8,8 +8,21 @@ defmodule AzimuttWeb.Api.SourceController do alias AzimuttWeb.Utils.CtxParams alias AzimuttWeb.Utils.JsonSchema alias AzimuttWeb.Utils.ProjectSchema + alias AzimuttWeb.Utils.SwaggerCommon action_fallback AzimuttWeb.Api.FallbackController + swagger_path :index do + tag("Sources") + summary("List project sources") + description("Get all the sources in a project.") + get("#{SwaggerCommon.project_path()}/sources") + SwaggerCommon.authorization() + SwaggerCommon.project_params() + + response(200, "OK", Schema.ref(:SourceItems)) + response(400, "Client Error") + end + def index(conn, %{"organization_id" => _organization_id, "project_id" => project_id} = params) do current_user = conn.assigns.current_user ctx = CtxParams.from_params(params) @@ -19,6 +32,22 @@ defmodule AzimuttWeb.Api.SourceController do do: conn |> render("index.json", sources: content["sources"], ctx: ctx) end + swagger_path :show do + tag("Sources") + summary("Get a source") + description("Get a source with its content (tables, relations and all).") + get("#{SwaggerCommon.project_path()}/sources/{source_id}") + SwaggerCommon.authorization() + SwaggerCommon.project_params() + + parameters do + source_id(:path, :string, "UUID of the source", required: true) + end + + response(200, "OK", Schema.ref(:Source)) + response(400, "Client Error") + end + def show(conn, %{"organization_id" => _organization_id, "project_id" => project_id, "source_id" => source_id} = params) do current_user = conn.assigns.current_user ctx = CtxParams.from_params(params) @@ -29,6 +58,35 @@ defmodule AzimuttWeb.Api.SourceController do do: conn |> render("show.json", source: source, ctx: ctx) end + swagger_path :create do + tag("Sources") + summary("Create a source") + description("Create a source with its content on a project. Can't be an `AmlEditor` source.") + post("#{SwaggerCommon.project_path()}/sources") + SwaggerCommon.authorization() + SwaggerCommon.project_params() + + parameters do + payload(:body, :object, "Source content", + required: true, + schema: + Schema.new do + properties do + name(:string, "Source name", required: true, example: "azimutt_dev") + kind(Schema.ref(:SourceKind), "Source kind", required: true) + tables(:array, "Tables of the source", required: true, items: Schema.ref(:SourceTable)) + relations(:array, "Relations of the source", required: true, items: Schema.ref(:SourceRelation)) + types(:array, "Custom types of the source", items: Schema.ref(:SourceType)) + enabled(:boolean, "If the source is enabled in the project", example: true) + end + end + ) + end + + response(200, "OK", Schema.ref(:Source)) + response(400, "Client Error") + end + def create(conn, %{"organization_id" => _organization_id, "project_id" => project_id} = params) do now = DateTime.utc_now() ctx = CtxParams.from_params(params) @@ -61,6 +119,34 @@ defmodule AzimuttWeb.Api.SourceController do do: conn |> render("show.json", source: source, ctx: ctx) end + swagger_path :update do + tag("Sources") + summary("Update a source") + description("Update a source when it has changed. Can be a good idea to do that in your CI using the Azimutt CLI.") + put("#{SwaggerCommon.project_path()}/sources/{source_id}") + SwaggerCommon.authorization() + SwaggerCommon.project_params() + + parameters do + source_id(:path, :string, "UUID of the source", required: true) + + payload(:body, :object, "Source content", + required: true, + schema: + Schema.new do + properties do + tables(:array, "Tables of the source", required: true, items: Schema.ref(:SourceTable)) + relations(:array, "Relations of the source", required: true, items: Schema.ref(:SourceRelation)) + types(:array, "Custom types of the source", items: Schema.ref(:SourceType)) + end + end + ) + end + + response(200, "OK", Schema.ref(:Source)) + response(400, "Client Error") + end + def update(conn, %{"organization_id" => _organization_id, "project_id" => project_id, "source_id" => source_id} = params) do now = DateTime.utc_now() ctx = CtxParams.from_params(params) @@ -90,6 +176,22 @@ defmodule AzimuttWeb.Api.SourceController do do: conn |> render("show.json", source: source, ctx: ctx) end + swagger_path :delete do + tag("Sources") + summary("Delete a source") + description("As you guessed ^^ It returns the deleted source.") + PhoenixSwagger.Path.delete("#{SwaggerCommon.project_path()}/sources/{source_id}") + SwaggerCommon.authorization() + SwaggerCommon.project_params() + + parameters do + source_id(:path, :string, "UUID of the source", required: true) + end + + response(200, "OK", Schema.ref(:Source)) + response(400, "Client Error") + end + def delete(conn, %{"organization_id" => _organization_id, "project_id" => project_id, "source_id" => source_id} = params) do now = DateTime.utc_now() ctx = CtxParams.from_params(params) @@ -119,4 +221,191 @@ defmodule AzimuttWeb.Api.SourceController do |> Map.put("relations", params["relations"]) |> Mapx.put_no_nil("types", params["types"]) end + + def swagger_definitions do + %{ + SourceKind: + swagger_schema do + description("The kind of a source") + + properties do + kind(:string, "Kind of source", + enum: ["AmlEditor", "DatabaseConnection", "SqlLocalFile", "SqlRemoteFile", "PrismaLocalFile", "PrismaRemoteFile", "JsonLocalFile", "JsonRemoteFile"], + required: true, + example: "DatabaseConnection" + ) + + url(:string, "Database url for DatabaseConnection kind, file url for remote kinds (SqlRemoteFile, PrismaRemoteFile & JsonRemoteFile)", + example: "postgresql://postgres:postgres@localhost:5432/azimutt_dev" + ) + + size(:integer, "File size for file kinds (SqlLocalFile, SqlRemoteFile, PrismaLocalFile, PrismaRemoteFile, JsonLocalFile & JsonRemoteFile)", example: 1324) + name(:string, "File name for local kinds (SqlLocalFile, PrismaLocalFile & JsonLocalFile)", example: "structure.sql") + modified(:integer, "File last updated at for local kinds (SqlLocalFile, PrismaLocalFile & JsonLocalFile)", example: 1_682_781_320_168) + end + + example([ + %{kind: "AmlEditor"}, + %{kind: "DatabaseConnection", url: "postgresql://postgres:postgres@localhost:5432/azimutt_dev"}, + %{kind: "SqlRemoteFile", url: "https://azimutt.app/elm/samples/basic.sql", size: 1764}, + %{kind: "PrismaRemoteFile", url: "https://azimutt.app/elm/samples/basic.prisma", size: 1511}, + %{kind: "JsonRemoteFile", url: "https://azimutt.app/elm/samples/basic.json", size: 4777}, + %{kind: "SqlLocalFile", name: "structure.sql", size: 1764, modified: 1_682_781_320_168}, + %{kind: "PrismaLocalFile", name: "schema.prisma", size: 1511, modified: 1_682_781_320_168}, + %{kind: "JsonLocalFile", name: "azimutt.json", size: 4777, modified: 1_682_781_320_168} + ]) + end, + SourceItem: + swagger_schema do + description("A project source without its content.") + + properties do + id(:string, "Unique identifier", required: true, example: "cf29f83c-ab6d-460d-b625-1930c8ac17e2") + name(:string, "Source name", required: true, example: "azimutt_dev") + kind(Schema.ref(:SourceKind), "Source kind", required: true) + createdAt(:integer, "Creation timestamp", required: true, example: 1_682_781_320_168) + updatedAt(:integer, "Update timestamp", required: true, example: 1_682_781_320_168) + end + end, + SourceComment: + swagger_schema do + description("A SQL comment from the source") + + properties do + text(:string, "The comment text", required: true, example: "A SQL comment here") + end + end, + SourceColumn: + swagger_schema do + description("A table column in a source") + + properties do + name(:string, "The column name", required: true, example: "role") + type(:string, "The column type", required: true, example: "varchar(10)") + nullable(:boolean, "If the column is nullable", required: true, example: false) + default(:string, "The column default value", example: "admin") + comment(Schema.ref(:SourceComment), "The column comment in the source") + values(:array, "Some possible values from the column", items: %{type: :string}, example: ["guest", "admin"]) + columns(:array, "Nested columns of this column, for JSON", items: Schema.ref(:SourceColumn)) + end + end, + SourcePrimaryKey: + swagger_schema do + description("A table primary key") + + properties do + name(:text, "The primary key name", example: "users_pk") + columns(:array, "List of columns in the primary key, can be path", required: true, items: %{type: :string}, example: ["user_id", "role_id"]) + end + end, + SourceIndexUnique: + swagger_schema do + description("A table unique index") + + properties do + name(:text, "The unique index name", required: true, example: "project_slug_uniq") + columns(:array, "List of columns in the index, can be path", required: true, items: %{type: :string}, example: ["organization_id", "slug"]) + definition(:text, "The definition of the index", example: "USING btree (organization_id, slug)") + end + end, + SourceIndex: + swagger_schema do + description("A table index") + + properties do + name(:text, "The index name", required: true, example: "user_name_index") + columns(:array, "List of columns in the index, can be path", required: true, items: %{type: :string}, example: ["first_name", "last_name"]) + definition(:text, "The definition of the index", example: "USING btree (first_name, last_name)") + end + end, + SourceCheckConstraint: + swagger_schema do + description("A table check constraint") + + properties do + name(:text, "The check constraint name", required: true, example: "users_age_chk") + columns(:array, "List of columns in the constraint, can be path", required: true, items: %{type: :string}, example: ["age"]) + predicate(:text, "The predicate of the constraint", example: "age > 0") + end + end, + SourceTable: + swagger_schema do + description("A table in a source") + + properties do + schema(:string, "The table schema", required: true, example: "public") + table(:string, "The table name", required: true, example: "users") + view(:boolean, "If the table is a view", required: true, example: false) + columns(:array, "The columns of the table", required: true, items: Schema.ref(:SourceColumn)) + comment(Schema.ref(:SourcePrimaryKey), "The primary key of the table") + uniques(:array, "The unique indexes of the table", items: Schema.ref(:SourceIndexUnique)) + indexes(:array, "The indexes of the table", items: Schema.ref(:SourceIndex)) + checks(:array, "The checks of the table", items: Schema.ref(:SourceCheckConstraint)) + comment(Schema.ref(:SourceComment), "The table comment in the source") + end + end, + SourceColumnRef: + swagger_schema do + description("A relation in a source") + + properties do + table(:string, "The table id referenced (ex: schema.table)", required: true, example: "public.users") + column(:string, "The column path referenced (ex: name or details:theme)", required: true, example: "details:theme") + end + end, + SourceRelation: + swagger_schema do + description("A relation in a source") + + properties do + name(:string, "Relation name", required: true, example: "events_created_by_fk") + src(Schema.ref(:SourceColumnRef), "Relation source", required: true) + ref(Schema.ref(:SourceColumnRef), "Relation reference", required: true) + end + end, + SourceType: + swagger_schema do + description("A custom type in a source") + + properties do + schema(:string, "The type schema", required: true, example: "public") + name(:string, "The type name", required: true, example: "role") + + value( + Schema.new do + properties do + enum(:array, "Values for the enum type", items: %{type: :string}, example: ["admin", "guest"]) + definition(:string, "The definition of the type", example: "RANGE (subtype = float8, subtype_diff = float8mi)") + end + end, + "The type value, either a definition or an enum", + required: true, + example: [%{enum: ["admin", "guest"]}, %{definition: "RANGE (subtype = float8, subtype_diff = float8mi)"}] + ) + end + end, + SourceContent: + swagger_schema do + description("The content of a Source") + + properties do + content(:array, "Source original content, list of lines in the file", items: %{type: :string}) + tables(:array, "Tables of the source", items: Schema.ref(:SourceTable)) + relations(:array, "Relations of the source", items: Schema.ref(:SourceRelation)) + types(:array, "Custom types of the source", items: Schema.ref(:SourceType)) + end + end, + Source: + swagger_schema do + description("A project Source") + Schema.all_of([Schema.ref(:SourceItem), Schema.ref(:SourceContent)]) + end, + SourceItems: + swagger_schema do + description("A collection of SourceItems") + type(:array) + items(Schema.ref(:SourceItem)) + end + } + end end diff --git a/backend/lib/azimutt_web/controllers/api/user_controller.ex b/backend/lib/azimutt_web/controllers/api/user_controller.ex index a3fdb11fd..145caa3bc 100644 --- a/backend/lib/azimutt_web/controllers/api/user_controller.ex +++ b/backend/lib/azimutt_web/controllers/api/user_controller.ex @@ -1,10 +1,44 @@ defmodule AzimuttWeb.Api.UserController do use AzimuttWeb, :controller use PhoenixSwagger + alias AzimuttWeb.Utils.SwaggerCommon action_fallback AzimuttWeb.Api.FallbackController + swagger_path :current do + tag("Users") + summary("Get the current user") + description("Fetch the user which is logged in.") + get("/users/current") + SwaggerCommon.authorization() + + response(200, "OK", Schema.ref(:User)) + response(400, "Client Error") + end + def current(conn, _params) do current_user = conn.assigns.current_user conn |> render("show.json", user: current_user) end + + def swagger_definitions do + %{ + User: + swagger_schema do + description("An User in Azimutt") + + properties do + id(:string, "Unique identifier", format: "uuid", required: true, example: "11bd9544-d56a-43d7-9065-6f1f25addf8a") + slug(:string, "User slug", required: true, example: "loic-knuchel") + name(:string, "User name", required: true, example: "Loïc Knuchel") + email(:string, "User email", format: "email", required: true, example: "loic@azimutt.app") + avatar(:string, "User avatar", format: "uri", required: true, example: "https://avatars.githubusercontent.com/u/653009") + github_username(:string, "User github", example: "loicknuchel") + twitter_username(:string, "User twitter", example: "loicknuchel") + is_admin(:boolean, "If the user has admin rights", required: true, example: false) + last_signin(:string, "Last time the user signed in", format: "date-time", required: true, example: "2024-01-07T08:34:29.582485Z") + created_at(:string, "When the user was created", format: "date-time", required: true, example: "2023-04-27T17:55:11.612429Z") + end + end + } + end end diff --git a/backend/lib/azimutt_web/controllers/user_auth.ex b/backend/lib/azimutt_web/controllers/user_auth.ex index 1d446b4b9..7e9cfd86a 100644 --- a/backend/lib/azimutt_web/controllers/user_auth.ex +++ b/backend/lib/azimutt_web/controllers/user_auth.ex @@ -5,6 +5,7 @@ defmodule AzimuttWeb.UserAuth do import Phoenix.Controller require Logger alias Azimutt.Accounts + alias Azimutt.Accounts.UserAuthToken alias Azimutt.CleverCloud alias Azimutt.Heroku alias Azimutt.Tracking @@ -125,13 +126,13 @@ defmodule AzimuttWeb.UserAuth do and remember me token. """ def fetch_current_user(conn, _opts) do - auth_token = conn.params["auth-token"] || conn.req_headers |> Enum.find_value(fn {key, value} -> if key == "auth-token", do: value end) + auth_token = conn.params["auth-token"] || conn.req_headers |> Enum.find_value(fn {key, value} -> if key == "auth-token" && UserAuthToken.is_valid?(value), do: value end) {user_token, conn} = ensure_user_token(conn) user = cond do - user_token -> Accounts.get_user_by_session_token(user_token) auth_token -> Accounts.get_user_by_auth_token(auth_token, DateTime.utc_now()) + user_token -> Accounts.get_user_by_session_token(user_token) true -> {:ok, nil} end |> Result.or_else(nil) diff --git a/backend/lib/azimutt_web/router.ex b/backend/lib/azimutt_web/router.ex index 5dde08288..1ad5640bf 100644 --- a/backend/lib/azimutt_web/router.ex +++ b/backend/lib/azimutt_web/router.ex @@ -194,7 +194,7 @@ defmodule AzimuttWeb.Router do resources("/events", Admin.EventController, param: "event_id", only: [:index, :show]) end - scope "/api/v1" do + scope "/api/v1/swagger" do forward("/", PhoenixSwagger.Plug.SwaggerUI, otp_app: :azimutt, swagger_file: "swagger.json") end diff --git a/backend/lib/azimutt_web/utils/project_schema.ex b/backend/lib/azimutt_web/utils/project_schema.ex index 00c87581f..cbbce05b7 100644 --- a/backend/lib/azimutt_web/utils/project_schema.ex +++ b/backend/lib/azimutt_web/utils/project_schema.ex @@ -207,5 +207,5 @@ defmodule AzimuttWeb.Utils.ProjectSchema do def type, do: @type_schema def column_meta, do: @column_meta def table_meta, do: @table_meta - def metadata, do: @project_meta + def metadata, do: @metadata end diff --git a/backend/lib/azimutt_web/utils/swagger_common.ex b/backend/lib/azimutt_web/utils/swagger_common.ex index d7505bfc6..08b2e141d 100644 --- a/backend/lib/azimutt_web/utils/swagger_common.ex +++ b/backend/lib/azimutt_web/utils/swagger_common.ex @@ -1,17 +1,19 @@ defmodule AzimuttWeb.Utils.SwaggerCommon do @moduledoc "Common parameter declarations for phoenix swagger" - alias PhoenixSwagger.Path.PathObject import PhoenixSwagger.Path + alias AzimuttWeb.Utils.SwaggerCommon + alias PhoenixSwagger.Path.PathObject + + def authorization(%PathObject{} = path), + do: path |> parameter("auth-token", :header, :string, "Your auth token, needed if you don't have the auth cookie, create it from user settings") + + def organization_path, do: "/organizations/{organization_id}" - def authorization(%PathObject{} = path) do - path |> parameter("auth-token", :header, :string, "Your auth token, create it from user settings") - end + def organization_params(%PathObject{} = path), + do: path |> parameter("organization_id", :path, :string, "UUID of your organization", format: "uuid", required: true) - def project_path, do: "/organizations/{organization_id}/projects/{project_id}" + def project_path, do: "#{SwaggerCommon.organization_path()}/projects/{project_id}" - def project_params(%PathObject{} = path) do - path - |> parameter("organization_id", :path, :string, "UUID of your organization", format: "uuid", required: true) - |> parameter("project_id", :path, :string, "UUID of your project", format: "uuid", required: true) - end + def project_params(%PathObject{} = path), + do: path |> SwaggerCommon.organization_params() |> parameter("project_id", :path, :string, "UUID of your project", format: "uuid", required: true) end