diff --git a/lib/CF.pm b/lib/CF.pm
index 28c9c9d54649d131e0d475ebe6b1572ce85cd21b..37e1da3bb76d05fd48b50cc4fc2c874c38b6402d 100644
--- a/lib/CF.pm
+++ b/lib/CF.pm
@@ -1,15 +1,15 @@
 package CF;
 use Mojo::Base 'Mojolicious';
 use Mojo::Pg;
+use Mojo::JWT;
 use CF::Schema;
 
 # This method will run once at server start
 sub startup {
     my $self = shift;
 
-    $self->plugin('CF::Helpers::Core');
-
     my $cfg = $self->plugin('Config' => { file => 'cf.conf'} );
+    $self->helper( cfg => sub { return $cfg; } );
 
     # Konfigurace z ENV ma prednost
     KEY:
@@ -25,7 +25,7 @@ sub startup {
         ->username($cfg->{db_username})
         ->password($cfg->{db_password})
     ;
-    $pg->migrations->from_file($self->home . '/sql/migrations.sql');
+    $pg->migrations->from_dir($self->home . '/sql');
     $pg->migrations->migrate();
     $self->helper( pg => sub { return $pg; } );
 
@@ -37,13 +37,50 @@ sub startup {
     });
     $self->helper( schema => sub { return $schema; } );
 
+    $self->plugin('CF::Helpers::Core');
+    $self->plugin('CF::Helpers::Auth');
+
     $self->plugin("OpenAPI" => {
         url    => $self->home . '/openapi.yaml',
         schema => 'v3',
         plugins                        => [qw(+SpecRenderer +Cors +Security)],
         render_specification           => 1,
         render_specification_for_paths => 1,
-        default_response_codes         => [400, 404, 500, 501],
+        default_response_codes         => [400, 401, 403, 404, 500, 501],
+
+        security => {
+            Bearer => sub {
+                my ($c, $definition, $scopes, $cb ) = @_;
+
+                my $key = $c->req->headers->authorization;
+
+                # moznost nepovinneho bez tokenu
+                return $c->$cb() if $scopes->[0] && $scopes->[0] eq 'optional' && ! $key;
+
+                return $c->$cb('Authorization header not present') if ! $key;
+                return $c->$cb('Unsupported authorization type') if $key !~ s/Bearer\s+//i;
+
+                $c->oauth_token($key);
+
+                if (! $c->user ) {
+                    return $c->$cb('Invalid user');
+                }
+
+                my $user = $c->schema->resultset('User')->find_or_create(
+                    $c->user, { key => 'uuid'}
+                );
+                $c->stash->{user}{id} = $user->id;
+
+                return $c->$cb() if ! scalar @{ $scopes };
+
+                ROLE:
+                foreach my $role ( @{ $scopes } ) {
+                    return $c->$cb() if $c->user_roles->{ $role };
+                }
+
+                return $c->$cb('Insufficient permissions');
+            }
+        }
     });
 
     $self->defaults(
diff --git a/lib/CF/Controller/Posts.pm b/lib/CF/Controller/Posts.pm
new file mode 100644
index 0000000000000000000000000000000000000000..17b4bc8e5290a8f2f43ade6ef1739d2b91dc2d95
--- /dev/null
+++ b/lib/CF/Controller/Posts.pm
@@ -0,0 +1,27 @@
+package CF::Controller::Posts;
+use Mojo::Base 'Mojolicious::Controller';
+
+sub create {
+    my $c    = shift->openapi->valid_input or return;
+    my $args = $c->req->json;
+
+    # Navrh postupu muze predlozit jenom clen
+    if ( $args->{type} == 0 && ! $c->user_roles->{'xember'} ) {
+        return $c->error(401, 'Insufficient permissions');
+    }
+
+    my $post = $c->schema->resultset('Post')->create({
+        user_id => $c->user->{id},
+        type    => $args->{type},
+        content => $args->{content},
+    });
+
+    ### TODO: Notify
+
+    $c->render(
+        status  => 201,
+        openapi => { id => $post->id },
+    );
+};
+
+1;
diff --git a/lib/CF/Helpers/Auth.pm b/lib/CF/Helpers/Auth.pm
new file mode 100644
index 0000000000000000000000000000000000000000..50db7ee07de96a789d629d83e2ef68ae8b1918ee
--- /dev/null
+++ b/lib/CF/Helpers/Auth.pm
@@ -0,0 +1,100 @@
+package CF::Helpers::Auth;
+
+use base 'Mojolicious::Plugin';
+use feature 'signatures';
+no warnings qw{ experimental::signatures };
+
+use Mojo::UserAgent;
+use Mojo::JWT;
+
+use constant KEY_FORMAT => "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----";
+use constant REGIONS    => qr{^(jhc|jhm|kvk|lbk|msk|olk|pak|pha|plk|stc|ulk|vys|zlk|khk):(f|regp)$};
+
+sub register ( $class, $self, $conf) {
+
+    my $ua  = Mojo::UserAgent->new();
+    my ( $jwt, $groups);
+
+    $self->helper( jwt => sub {
+        if ( ! $jwt ) {
+            my $res;
+            eval { $res = $ua->get( $self->cfg->{oauth_url} )->result; };
+
+            if (! $@ && $res->is_success) {
+                $jwt = Mojo::JWT->new(
+                    public => sprintf( KEY_FORMAT,  $res->json->{public_key} )
+                );
+            }
+        }
+        return $jwt;
+    });
+
+    $self->helper( oauth_groups => sub ( $c ) {
+        if ( ! $groups ) {
+            my $res;
+            eval { $res = $ua->get( $self->cfg->{groups_url} )->result; };
+
+            if (! $@ && $res->is_success) {
+                my $json = $res->json;
+                $groups = { map { $_->{code} => $_->{name} } @{ $json } };
+            }
+        }
+        return $groups;
+    });
+
+    $self->helper( oauth_token => sub ( $c, $token='' ) {
+        $c->stash->{token} //= $token;
+        return $c->stash->{token};
+    });
+
+    $self->helper( oauth_claims => sub ( $c ) {
+        if ( ! $c->stash->{claims}) {
+            return undef if ! ($c->jwt && $c->oauth_token);
+
+            my $claims;
+            eval { $claims = $c->jwt->decode( $c->oauth_token );  };
+
+            if ( $@ ) {
+                $c->app->log->warn("Invalid token ($@)");
+            }
+
+             $c->stash->{claims} = $claims;
+        }
+
+        return $c->stash->{claims};
+    });
+
+    $self->helper( oauth_main_group_name => sub ( $c ) {
+        my $claims = $c->oauth_claims // return;
+
+        GROUP:
+        foreach my $group ( sort @{ $claims->{groups} } ) {
+            return $c->oauth_groups->{ $group } if $group =~ REGIONS;
+        }
+    });
+
+    $self->helper( user => sub ( $c ) {
+        my $claims = $c->oauth_claims // return;
+
+        if ( ! $c->stash->{user} ) {
+            $c->stash->{user} = {
+                uuid     => $claims->{sub},
+                username => $claims->{preferred_username},
+                name     => $claims->{name},
+                main_group_name => $c->oauth_main_group_name(),
+            };
+        }
+        return $c->stash->{user};
+    });
+
+    $self->helper( user_roles => sub ( $c ) {
+        my $claims = $c->oauth_claims // return;
+        $c->stash->{user_roles} //= { map { $_ => 1 } @{ $claims->{roles} // [] }};
+        return $c->stash->{user_roles};
+    });
+
+}
+
+1;
+
+__END__
diff --git a/lib/CF/Schema/Result/Announcement.pm b/lib/CF/Schema/Result/Announcement.pm
new file mode 100644
index 0000000000000000000000000000000000000000..97b05ab1a19e2de892e869abbc3ba41bd0ae9621
--- /dev/null
+++ b/lib/CF/Schema/Result/Announcement.pm
@@ -0,0 +1,35 @@
+package CF::Schema::Result::Announcement;
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+our $VERSION = 1;
+
+__PACKAGE__->table('announcements');
+
+__PACKAGE__->add_columns(
+    id => {
+        data_type         => 'integer',
+        is_auto_increment => 1,
+        is_nullable       => 0,
+        sequence          => 'uid_seq'
+    },
+    qw(
+        datetime
+        is_archived
+        user_id
+        type
+        state
+        content
+        link
+        related_post_id
+    ),
+);
+
+__PACKAGE__->set_primary_key('id');
+
+1;
+
+
diff --git a/lib/CF/Schema/Result/Post.pm b/lib/CF/Schema/Result/Post.pm
new file mode 100644
index 0000000000000000000000000000000000000000..17ba0b9ecdfcc5889575c095d760f9760ce8b440
--- /dev/null
+++ b/lib/CF/Schema/Result/Post.pm
@@ -0,0 +1,34 @@
+package CF::Schema::Result::Post;
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+our $VERSION = 1;
+
+__PACKAGE__->table('posts');
+
+__PACKAGE__->add_columns(
+    id => {
+        data_type         => 'integer',
+        is_auto_increment => 1,
+        is_nullable       => 0,
+        sequence          => 'uid_seq'
+    },
+    qw(
+        datetime
+        is_archived
+        user_id
+        type
+        state
+        content
+        ranking_likes
+        ranking_dislikes
+    ),
+);
+
+__PACKAGE__->set_primary_key('id');
+
+1;
+
diff --git a/lib/CF/Schema/Result/Post_view.pm b/lib/CF/Schema/Result/Post_view.pm
new file mode 100644
index 0000000000000000000000000000000000000000..a72981125faa1b63416e9cce05dc9a04342d0595
--- /dev/null
+++ b/lib/CF/Schema/Result/Post_view.pm
@@ -0,0 +1,47 @@
+package CF::Schema::Result::Post_view;
+
+use strict;
+use warnings;
+
+use base 'CF::Schema::Result::Post';
+
+our $VERSION = 1;
+
+__PACKAGE__->table('posts_view');
+
+__PACKAGE__->add_columns(
+    qw(
+        score
+        user_name
+        group_name
+    ),
+);
+
+__PACKAGE__->set_primary_key('id');
+
+sub format {
+    my $self = shift;
+
+    my $post = {
+        id         => $self->id,
+        datetime   => $self->datetime,
+        type       => $self->type,
+        is_archive => $self->is_archived,
+        author => {
+            name  => $self->user_name,
+            group => $self->group_name,
+        },
+        ranking => {
+            score    => $self->score,
+            likes    => $self->likes,
+            dislikes => $self->dislikes,
+            my_vote  => 0, #TODO
+        }
+    };
+
+    return $post;
+
+}
+
+
+1;