diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 85dd4f992a6b385a81eeb0ec10c39e6322adfb95..7e7dbb6259f6a0be41715d88d548f717e04e0645 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,7 +2,7 @@ image: docker:19.03.12
 
 variables:
   DOCKER_TLS_CERTDIR: "/certs"
-  IMAGE_VER: 2.1.1
+  IMAGE_VER: 2.2.0
 
 services:
   - docker:19.03.12-dind
diff --git a/lib/CF/Controller/Events.pm b/lib/CF/Controller/Events.pm
index 0b5b92dc0ee477c5425020011a2326335cade929..f3e049a63d1a7c73cf217d1d69fb97acabb320dc 100644
--- a/lib/CF/Controller/Events.pm
+++ b/lib/CF/Controller/Events.pm
@@ -30,10 +30,7 @@ sub create ($c) {
 sub get ($c) {
     $c->openapi->valid_input or return;
 
-    my $event = $c->schema->resultset('Event')->find({
-        id => $c->stash->{id},
-    });
-    return $c->error(404, 'Event not found') if ! $event || $event->deleted;
+    my $event = $c->_get() || return;
 
     my $formatted = $event->format();
     $formatted->{acl} = [];
@@ -96,14 +93,9 @@ sub update ($c) {
 
     my $args = $c->req->json;
 
-    my $event = $c->schema->resultset('Event')->find($c->stash->{id});
-    return $c->error(404, 'Event not found') if ! $event;
-
+    my $event  = $c->_get('chairman');
     my $update = $c->prepare_update_data( $event, $c->req->json );
-
-    my $guard  = $c->schema->txn_scope_guard;
     $event->update({ %{ $update }, });
-    $guard->commit;
 
     $c->render(status => 204, text => '');
 }
@@ -111,14 +103,26 @@ sub update ($c) {
 sub delete ($c) {
     $c->openapi->valid_input or return;
 
-    my $event = $c->schema->resultset('Event')->find($c->stash->{id});
-    return $c->error(404, 'Event not found') if ! $event;
-
-    my $guard  = $c->schema->txn_scope_guard;
+    my $event  = $c->_get('chairman') || return;
     $event->update({ deleted => \'now()', });
-    $guard->commit;
 
     $c->render(status => 204, text => '');
 }
 
+sub _get ($c, $role='') {
+
+    my $event = $c->schema->resultset('Event')->find({
+        $c->stash->{id}
+    });
+
+    return $c->error(404, 'Event not found') if ! $event || $event->deleted;
+
+    if ( $role ) {
+        my $roles = $event->user_roles( $c->user );
+        return $c->error(403, 'Access denied') if ! exists $roles->{$role};
+    }
+
+    return $event;
+}
+
 1;
diff --git a/lib/CF/Schema/Result/Event.pm b/lib/CF/Schema/Result/Event.pm
index 01e73357235e1c8b55e6241e004b88c0d097787b..471c1f68c7c0f6e5f1a43695e70e4228923bc962 100644
--- a/lib/CF/Schema/Result/Event.pm
+++ b/lib/CF/Schema/Result/Event.pm
@@ -23,6 +23,7 @@ __PACKAGE__->add_columns(
         type
         state
         owner_id
+        is_opened
         is_published
         start
         finish
@@ -54,6 +55,7 @@ sub format ($self) {
         id              => $self->id,
         type            => $self->type,
         state           => $self->state,
+        is_opened       => $self->is_opened ? \1 : \0,
         is_published    => $self->is_published ? \1 : \0,
         start           => $self->start,
         finish          => $self->finish,
diff --git a/openapi.yaml b/openapi.yaml
index 744a0cac2f4ed646848fde854c72787602c380ac..2096e7c84ce4a1b30880a77936ca7c24d1dc7bb0 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -91,6 +91,8 @@ components:
         state:
           type: integer
           enum: [0, 1, 2]
+        is_opened:
+          type: boolean
         is_published:
           type: boolean
         start:
@@ -343,6 +345,9 @@ paths:
                   type: integer
                   enum: [1, 2, 3]
                   example: 1
+                is_opened:
+                  type: boolean
+                  example: true
                 start:
                   type: string
                   maxLength: 20
@@ -467,7 +472,7 @@ paths:
         in: path
         required: true
         example: 100345
-        description: "ID"
+        description: "Event ID"
         schema:
           type: integer
       requestBody:
@@ -479,6 +484,9 @@ paths:
                 type:
                   type: integer
                   enum: [1, 2, 3]
+                is_opened:
+                  type: boolean
+                  example: true
                 state:
                   type: integer
                   enum: [0, 1, 2]
@@ -513,7 +521,7 @@ paths:
       security:
         - Bearer: ['organizer']
       tags:
-        - event
+        - events
       summary: "Smazat udalost"
       operationId: deleteEvent
       parameters:
@@ -527,6 +535,110 @@ paths:
         204:
           description: Event deleted
 
+  /events/{id}/roles:
+    post:
+      x-mojo-to: events#role_add
+      security:
+        - Bearer: ['*']
+      tags:
+        - events
+      summary: "Priradit roli pro udalost"
+      operationId: addEventRole
+      parameters:
+      - name: id
+        in: path
+        required: true
+        example: 100345
+        description: "Event ID"
+        schema:
+          type: integer
+      requestBody:
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                subject_class:
+                  type: string
+                  enum: ['user', 'group']
+                  example: 'user'
+                subject:
+                  type: string
+                  example: 'cen:f'
+                role:
+                  type: string
+                  enum: ['chairman', 'member', 'guest', 'jitsi']
+                  example: 'member'
+              required:
+                - subject_class
+                - subject
+                - role
+      responses:
+        201:
+          description: Role set
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  id:
+                    type: integer
+                    description: ACL record id
+    delete:
+      x-mojo-to: events#role_delete
+      security:
+        - Bearer: ['*']
+      tags:
+        - events
+      summary: "Odebrat roli pro udalost"
+      operationId: deleteEventRole
+      parameters:
+      - name: id
+        in: path
+        required: true
+        example: 100345
+        description: "Event ID"
+        schema:
+          type: integer
+      - name: role_id
+        in: query
+        required: true
+        example: 200345
+        description: "Identifikator ACL zaznamu"
+        schema:
+          type: integer
+      responses:
+        204:
+          description: Role deleted
+
+  /events/{id}/banner:
+    post:
+      x-mojo-to: events#banner
+      security:
+        - Bearer: ['*']
+      tags:
+        - events
+      summary: "Uploadovat banner"
+      operationId: addEventBanner
+      parameters:
+      - name: id
+        in: path
+        required: true
+        example: 100345
+        description: "Event ID"
+        schema:
+          type: integer
+      requestBody:
+        content:
+          application/octet-stream:
+            schema:
+              # a binary file of any type
+              type: string
+              format: binary
+      responses:
+        201:
+          description: Banner uploaded
+
   /sso/subjects:
     get:
       x-mojo-to: SSO#subjects
diff --git a/sql/8/up.sql b/sql/8/up.sql
index dfeabb9186cb0ef4b2281597cf9afdc31bb9fdd6..a7c438c65d19f8bfc6988c32bc87e99016b30bbc 100644
--- a/sql/8/up.sql
+++ b/sql/8/up.sql
@@ -4,6 +4,7 @@ create table "events" (
     "type" smallint not null default 1, --1
     "state" integer not null default 0,
     "owner_id" integer not null,
+    "is_opened" bool not null default true,
     "is_published" bool not null default false,
     "start" timestamp(0),
     "finish" timestamp(0),