From 1edeb32ca9f3c27b4be9153fe18754b35be7c761 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andrej=20Rama=C5=A1euski?= <andrej@x2.cz>
Date: Tue, 2 Aug 2022 00:19:52 +0200
Subject: [PATCH] Pridana podpora openapi

---
 Dockerfile                    |  3 +-
 lib/PZ.pm                     | 40 +++++++++++++++-
 lib/PZ/Controller/Shortcut.pm | 14 ++++++
 lib/PZ/Helpers/Core.pm        | 86 +++++++++++++++++++++++++++++++++++
 lib/PZ/Schema/Result/User.pm  |  5 ++
 openapi.yaml                  | 62 +++++++++++++++++++++++++
 6 files changed, 207 insertions(+), 3 deletions(-)
 create mode 100644 lib/PZ/Helpers/Core.pm
 create mode 100644 openapi.yaml

diff --git a/Dockerfile b/Dockerfile
index 4096522..d6f1084 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,7 +27,8 @@ RUN cpanm \
     Mojo::Pg \
     Mojo::Redis \
     Mojo::JWT \
-    Mojolicious::Plugin::Authentication
+    Mojolicious::Plugin::Authentication \
+    Mojolicious::Plugin::OpenAPI
 
 ADD . /opt/PZ
 WORKDIR /opt/PZ
diff --git a/lib/PZ.pm b/lib/PZ.pm
index c9eae00..bc0549a 100644
--- a/lib/PZ.pm
+++ b/lib/PZ.pm
@@ -20,6 +20,7 @@ sub startup {
     # Delka session
     $self->sessions->default_expiration($cfg->{session}{lifetime});
 
+    $self->plugin('PZ::Helpers::Core');
     $self->plugin('PZ::Helpers::OIDC');
 
     my $redis = Mojo::Redis->new( 'redis://' . $cfg->{redis}{server} );
@@ -43,7 +44,10 @@ sub startup {
         autoload_user => 1,
         load_user => sub {
             my $c = shift;
-            return $c->session->{user};
+            my $user = $c->schema->resultset('User')->find({
+                id => $c->session->{user}{id},
+            });
+            return $user;
         },
         validate_user => sub {
             my $c = shift;
@@ -52,8 +56,40 @@ sub startup {
         },
     });
 
+    $self->plugin("OpenAPI" => {
+        url      => $self->home->rel_file("openapi.yaml"),
+        schema => 'v3',
+        plugins                        => [qw(+SpecRenderer +Cors +Security)],
+        render_specification           => 1,
+        render_specification_for_paths => 1,
+        default_response_codes         => [400, 401, 403, 404, 500, 501],
+
+        security => {
+            Token => sub {
+                my ($c, $definition, $scopes, $cb ) = @_;
+
+                my $token = $c->req->headers->header('X-Auth-Token');
+
+                return $c->$cb('Authorization header not present') if ! $token;
+
+                my $user = $c->schema->resultset('User')->find(
+                    { token => $token }
+                );
+
+                if (! $user ) {
+                    return $c->$cb('Invalid user');
+                }
+
+                $c->stash->{user} = $user;
+                return $c->$cb();
+            }
+        }
+    });
+
     # defautni globalni promenne ve stash
-    $self->defaults();
+    $self->defaults(
+        openapi_cors_allowed_origins => ['*'],
+    );
 
     # vypnuti cache templatu pri vyvoji
     $self->renderer->cache->max_keys(0) if $cfg->{dev_mode};
diff --git a/lib/PZ/Controller/Shortcut.pm b/lib/PZ/Controller/Shortcut.pm
index 792af36..2a4a1c8 100644
--- a/lib/PZ/Controller/Shortcut.pm
+++ b/lib/PZ/Controller/Shortcut.pm
@@ -74,4 +74,18 @@ sub qr ($c) {
     $c->render( data => $png );
 }
 
+
+sub list ($c) {
+    my @shortcuts = ();
+
+    SHORTCUT:
+    foreach my $shortcut ( $c->stash->{user}->shortcuts ) {
+        push @shortcuts, $c->spec_filter(
+            { $shortcut->get_columns }, 'Shortcut'
+        );
+    }
+
+    $c->render(json => \@shortcuts );
+}
+
 1;
diff --git a/lib/PZ/Helpers/Core.pm b/lib/PZ/Helpers/Core.pm
new file mode 100644
index 0000000..f566d9a
--- /dev/null
+++ b/lib/PZ/Helpers/Core.pm
@@ -0,0 +1,86 @@
+package PZ::Helpers::Core;
+
+use base 'Mojolicious::Plugin';
+
+use YAML;
+
+sub register  {
+    my ($class, $self ) = @_;
+
+    $self->helper(error => sub {
+        my $c      = shift;
+        my $status = shift;
+        my $errors = [];
+
+        $c->cirpack_ws->rollback();
+
+        if ( scalar @_ == 2 ) {
+            $errors = [{ code => shift, message => shift }];
+        }
+        elsif ( ref $_[0] eq 'ARRAY' ) {
+            $errors = shift;
+        }
+        elsif ( ref $_[0] eq 'HASH' ) {
+            $errors = [ shift ];
+        }
+        else {
+            $errors = [{ message => shift, code => undef }];
+        }
+
+        $c->stash(
+            status  => $status,
+            openapi => { errors => $errors }
+        );
+
+        return undef;
+    });
+
+    $self->helper( trace => sub {
+        my $c      = shift;
+        my $data   = shift // '';
+
+        $data = Dump $data if ref $data;
+        $c->app->log->debug($data);
+
+    });
+
+    $self->helper( spec_filter => sub {
+        my $c     = shift;
+        my $data  = shift;
+        my $class = shift;
+
+        if (my $def =$c->openapi->spec("/components/schemas/$class")) {
+            my $filtered = {};
+
+            KEY:
+            foreach my $key ( keys %{ $def->{properties} } ) {
+                my $value    = $data->{$key};
+                my $nullable = 0;
+
+                my $types = $def->{properties}{$key}{type};
+
+                if ( ref $types eq 'ARRAY' ) {
+                    TYPE:
+                    foreach my $type ( @{ $types } ) {
+                        $nullable = 1, last if $type eq 'null';
+                    }
+                }
+
+                if ( $key =~ /^cirpack_/ && $value eq 'XXX' ) {
+                    $value  = $nullable ? undef : '';
+                }
+
+                $filtered->{$key} = $value;
+            }
+
+            $data = $filtered;
+
+        }
+
+        return $data;
+    });
+
+}
+
+1;
+
diff --git a/lib/PZ/Schema/Result/User.pm b/lib/PZ/Schema/Result/User.pm
index 58034cc..14b1102 100644
--- a/lib/PZ/Schema/Result/User.pm
+++ b/lib/PZ/Schema/Result/User.pm
@@ -39,6 +39,11 @@ __PACKAGE__->add_unique_constraint(
     'token' => [qw(token)]
 );
 
+__PACKAGE__->has_many(
+    shortcuts => 'PZ::Schema::Result::Shortcut',
+    { 'foreign.user_id' => 'self.id', },
+);
+
 sub set_token {
     my $self = shift;
     my $new  = shift;
diff --git a/openapi.yaml b/openapi.yaml
new file mode 100644
index 0000000..bb13336
--- /dev/null
+++ b/openapi.yaml
@@ -0,0 +1,62 @@
+openapi: 3.0.3
+
+info:
+  title: Piratský zkracovač
+  description:  Piratský zkracovač API
+  version: 1.15.1
+  license:
+    name: Artistic License 2.0
+    url: https://www.perlfoundation.org/artistic-license-20.html
+  contact:
+    name: Andrej Ramašeuski
+    email: andrej@x2.cz
+    url: https://pardubicky.pirati.cz/lide/andrej-ramaseuski/
+
+servers:
+    - url: https://z.pirati.cz/api
+      description: Production server
+    - url: http://127.0.0.1:3000/api
+      description: Test server
+
+components:
+  schemas:
+    Shortcut:
+      type: object
+      properties:
+        id:
+          type: integer
+        shortcut:
+          type: string
+          description: Zkratka
+        url:
+          type: string
+          description: URL pro přesměrování
+        code:
+          type: integer
+          description: Kód přesměrování
+  securitySchemes:
+    Token:
+      type: apiKey
+      in: header
+      name: X-Auth-Token
+
+paths:
+  /shortcuts:
+    get:
+      tags:
+      - shortcuts
+      security:
+        - Token: []
+      summary: "Seznam zkratek"
+      operationId: Shortcuts
+      x-mojo-to: shortcut#list
+      responses:
+        200:
+          description: Seznam zkratek
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Shortcut'
+
-- 
GitLab