Skip to content
Snippets Groups Projects
Verified Commit 241db43c authored by Andrej Ramašeuski's avatar Andrej Ramašeuski
Browse files

Posts management

parent 74edc206
No related branches found
No related tags found
No related merge requests found
package CF::Controller::Posts; package CF::Controller::Posts;
use Mojo::Base 'Mojolicious::Controller'; use Mojo::Base 'Mojolicious::Controller';
use Mojo::Pg::PubSub;
use feature 'signatures';
no warnings qw{ experimental::signatures };
sub create ($c) {
$c->openapi->valid_input or return;
sub create { my $args = $c->req->json;
my $c = shift->openapi->valid_input or return; my $pubsub = Mojo::Pg::PubSub->new(pg => $c->pg);
my $args = $c->req->json;
# Navrh postupu muze predlozit jenom clen # Navrh postupu muze predlozit jenom clen
if ( $args->{type} == 0 && ! $c->user_roles->{'xember'} ) { if ( $args->{type} == 0 && ! $c->user_roles->{member} ) {
return $c->error(401, 'Insufficient permissions'); return $c->error(401, 'Insufficient permissions');
} }
...@@ -16,7 +22,9 @@ sub create { ...@@ -16,7 +22,9 @@ sub create {
content => $args->{content}, content => $args->{content},
}); });
### TODO: Notify $pubsub->json('posts')->notify(
posts => $post->view->format()
);
$c->render( $c->render(
status => 201, status => 201,
...@@ -24,4 +32,171 @@ sub create { ...@@ -24,4 +32,171 @@ sub create {
); );
}; };
sub get ($c) {
$c->openapi->valid_input or return;
my $post = $c->schema->resultset('Post_view')->find($c->stash->{id});
return $c->error(404, 'Post not found') if ! $post;
$c->render(openapi => $c->spec_filter($post->format(), 'Post'));
}
sub list ($c) {
$c->openapi->valid_input or return;
my $args = $c->validation->output;
my ($cond, $attrs) = $c->search_parametrs( $args );
$cond->{type} = $args->{type};
if ( exists $args->{archived} && defined $args->{archived}) {
$cond->{is_archived} = $args->{archived};
}
# neschvalene navrhy postupu
if ( $args->{type} == 0 && ! $c->user_roles->{chairman} ) {
$cond->{state} = {'-in' => [1,2,3,4]}
}
my @posts = ();
my $count = $c->schema->resultset('Post_view')->count($cond);
if ( $count ) {
my $posts = $c->schema->resultset('Post_view')->search($cond, $attrs);
RECORD:
while ( my $post = $posts->next() ) {
push @posts, $c->spec_filter($post->format(), 'Post');
}
}
$c->render(json => {
data => \@posts,
total => $count,
});
}
sub update ($c) {
$c->openapi->valid_input or return;
my $args = $c->req->json;
my $pubsub = Mojo::Pg::PubSub->new(pg => $c->pg);
my $update = {};
my $post = $c->schema->resultset('Post')->find($c->stash->{id});
return $c->error(404, 'Post not found') if ! $post;
if ( ! $c->user_roles->{chairman} ) {
if ( $post->user_id != $c->user->{id} ) {
return $c->error(403, 'Access deined');
}
else {
delete $args->{is_archived};
delete $args->{state};
}
}
foreach my $key (qw(is_archoved state content)) {
$update->{$key} = $args->{$key} if exists $args->{$key};
}
my $guard = $c->schema->txn_scope_guard;
$post->add_to_history({
user_id => $c->user->{id},
datetime => $post->datetime,
content => $post->content,
});
$post->update( $update );
$pubsub->json('posts')->notify(
posts => $post->view->format()
);
$guard->commit;
$c->render(status => 204, text => '');
}
sub ranking ($c) {
$c->openapi->valid_input or return;
my $args = $c->validation->output;
my $pubsub = Mojo::Pg::PubSub->new(pg => $c->pg);
my $post = $c->schema->resultset('Post')->find($c->stash->{id});
return $c->error(404, 'Post not found') if ! $post;
my $user_ranking = $post->rankings({
user_id => $c->user->{id},
})->first;
my $update = {
ranking_likes => $post->ranking_likes,
ranking_dislikes => $post->ranking_dislikes,
};
my $user_ranking_update;
if ($user_ranking && $user_ranking->ranking == $args->{ranking} ) {
$user_ranking_update = 0;
}
else {
$user_ranking_update = $args->{ranking};
}
if ($user_ranking && $user_ranking->ranking == 1 ) {
$update->{ranking_likes}--;
$update->{ranking_dislikes}++ if $args->{ranking} == -1;
}
elsif ($user_ranking && $user_ranking->ranking == -1 ) {
$update->{ranking_dislikes}--;
$update->{ranking_likes}++ if $args->{ranking} == 1;
}
else {
$update->{ranking_likes}++ if $args->{ranking} == 1;
$update->{ranking_dislikes}++ if $args->{ranking} == -1;
}
my $guard = $c->schema->txn_scope_guard;
$post->update( $update );
if ( $user_ranking ) {
$user_ranking->update( { ranking => $user_ranking_update } );
}
else {
$post->add_to_rankings({
user_id => $c->user->{id},
ranking => $user_ranking_update,
});
}
$pubsub->json('posts')->notify(
posts => $post->view->format()
);
$guard->commit;
$c->render(status => 204, text => '');
}
sub ws {
my $c = shift;
$c->inactivity_timeout(300);
my $pubsub = Mojo::Pg::PubSub->new(pg => $c->pg);
$pubsub->listen(posts => sub($pubsub, $payload) {
# FILTER?
$c->send($payload);
});
$c->on(finish => sub ($c, $code, $reason = undef) {
$pubsub->unlisten('streams');
$c->app->log->debug("WebSocket closed with status $code");
});
}
1; 1;
...@@ -30,5 +30,23 @@ __PACKAGE__->add_columns( ...@@ -30,5 +30,23 @@ __PACKAGE__->add_columns(
__PACKAGE__->set_primary_key('id'); __PACKAGE__->set_primary_key('id');
1; __PACKAGE__->has_one( view => 'CF::Schema::Result::Post_view', 'id');
__PACKAGE__->belongs_to(
user => 'Zircon::Schema::Result::User',
{
'foreign.id' => 'self.user_id',
},
);
__PACKAGE__->has_many(
history => 'CF::Schema::Result::PostHistory',
{ 'foreign.post_id' => 'self.id', },
);
__PACKAGE__->has_many(
rankings => 'CF::Schema::Result::PostRanking',
{ 'foreign.post_id' => 'self.id', },
);
1;
package CF::Schema::Result::PostHistory;
use strict;
use warnings;
use base 'DBIx::Class::Core';
our $VERSION = 1;
__PACKAGE__->table('posts_history');
__PACKAGE__->add_columns(
id => {
data_type => 'integer',
is_auto_increment => 1,
is_nullable => 0,
sequence => 'uid_seq'
},
qw(
datetime
user_id
post_id
content
),
);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->has_one( view => 'CF::Schema::Result::Post_view', 'id');
__PACKAGE__->belongs_to(
user => 'Zircon::Schema::Result::User',
{
'foreign.id' => 'self.user_id',
},
);
__PACKAGE__->belongs_to(
post => 'Zircon::Schema::Result::Post',
{
'foreign.id' => 'self.post_id',
},
);
1;
package CF::Schema::Result::PostRanking;
use strict;
use warnings;
use base 'DBIx::Class::Core';
our $VERSION = 1;
__PACKAGE__->table('posts_ranking');
__PACKAGE__->add_columns(
id => {
data_type => 'integer',
is_auto_increment => 1,
is_nullable => 0,
sequence => 'uid_seq'
},
qw(
user_id
post_id
ranking
),
);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->add_unique_constraint(
'post_user' => [qw(user_id post_id)]
);
__PACKAGE__->has_one( view => 'CF::Schema::Result::Post_view', 'id');
__PACKAGE__->belongs_to(
user => 'Zircon::Schema::Result::User',
{
'foreign.id' => 'self.user_id',
},
);
__PACKAGE__->belongs_to(
post => 'Zircon::Schema::Result::Post',
{
'foreign.id' => 'self.post_id',
},
);
1;
...@@ -11,7 +11,7 @@ __PACKAGE__->table('posts_view'); ...@@ -11,7 +11,7 @@ __PACKAGE__->table('posts_view');
__PACKAGE__->add_columns( __PACKAGE__->add_columns(
qw( qw(
score ranking_score
user_name user_name
group_name group_name
), ),
...@@ -23,18 +23,20 @@ sub format { ...@@ -23,18 +23,20 @@ sub format {
my $self = shift; my $self = shift;
my $post = { my $post = {
id => $self->id, id => $self->id,
datetime => $self->datetime, datetime => $self->datetime,
type => $self->type, type => $self->type,
is_archive => $self->is_archived, state => $self->state,
content => $self->content,
is_archived => $self->is_archived,
author => { author => {
name => $self->user_name, name => $self->user_name,
group => $self->group_name, group => $self->group_name,
}, },
ranking => { ranking => {
score => $self->score, score => $self->ranking_score,
likes => $self->likes, likes => $self->ranking_likes,
dislikes => $self->dislikes, dislikes => $self->ranking_dislikes,
my_vote => 0, #TODO my_vote => 0, #TODO
} }
}; };
......
...@@ -24,6 +24,25 @@ components: ...@@ -24,6 +24,25 @@ components:
name: Authorization name: Authorization
in: header in: header
type: apiKey type: apiKey
parameters:
offset:
name: offset
in: query
description: The number of items to skip before starting to collect the result set
required: false
schema:
type: integer
default: 0
minimum: 0
limit:
name: limit
in: query
description: The numbers of items to return
required: false
schema:
type: integer
default: 100
minimum: 1
schemas: schemas:
ProgramScheduleEntry: ProgramScheduleEntry:
type: object type: object
...@@ -67,53 +86,30 @@ components: ...@@ -67,53 +86,30 @@ components:
type: string type: string
group: group:
type: string type: string
DiscussionPost: Post:
type: object type: object
description: Prispevek do rozpravy description: Prispevek
properties: properties:
id: id:
type: integer type: integer
readOnly: true readOnly: true
datetime: datetime:
type: string type: string
is_archived: type:
type: boolean
content:
type: string
author:
$ref: '#/components/schemas/Author'
ranking:
$ref: '#/components/schemas/Ranking'
ProposalPost:
type: object
description: NavrhPostupu
properties:
id:
type: integer type: integer
readOnly: true readOnly: true
datetime: enum: [0, 1]
state:
type: integer
enum: [0, 1, 2, 3, 4]
content:
type: string type: string
is_archived: is_archived:
type: boolean type: boolean
content:
type: string
author: author:
$ref: '#/components/schemas/Author' $ref: '#/components/schemas/Author'
ranking: ranking:
$ref: '#/components/schemas/Ranking' $ref: '#/components/schemas/Ranking'
# responses:
# Unauthorized:
# description: Unauthorized
# schema:
# $ref: '#/definitions/ErrorResponse'
# Forbidden:
# description: Forbidden
# schema:
# $ref: '#/definitions/ErrorResponse'
# NotFound:
# description: The specified resource was not found
# schema:
# $ref: '#/definitions/ErrorResponse'
paths: paths:
/program: /program:
...@@ -166,23 +162,133 @@ paths: ...@@ -166,23 +162,133 @@ paths:
id: id:
type: integer type: integer
description: Post id description: Post id
get: get:
x-mojo-to: posts#list x-mojo-to: posts#list
security: security:
- Bearer: [] - Bearer: ['optional', 'member', 'regp']
tags: tags:
- posts - posts
summary: "Zpravy" summary: "Zpravy"
operationId: getPosts operationId: getPosts
parameters:
- $ref: '#/components/parameters/offset'
- $ref: '#/components/parameters/limit'
- name: type
in: query
description: "Typ zpravy"
required: true
schema:
type: integer
enum: [0, 1]
- name: archived
in: query
description: "Zobrazovat archivovane"
required: false
schema:
type: boolean
- name: sort
description: "Razeni"
in: query
style: form
schema:
type: array
uniqueItems: true
items:
type: string
enum: [ type, datetime, -type, -datetime,]
default: [ -datetime ]
responses: responses:
200: 200:
description: Posts description: Posts
content: content:
application/json: application/json:
schema: schema:
type: array type: object
items: properties:
oneOf: count:
- $ref: '#/components/schemas/DiscussionPost' type: integer
- $ref: '#/components/schemas/ProposalPost' description: Celkovy pocet
data:
type: array
items:
$ref: '#/components/schemas/Post'
/posts/{id}:
get:
x-mojo-to: posts#get
tags:
- posts
summary: "Detail zpravy"
operationId: getPost
responses:
200:
description: Post
content:
application/json:
schema:
$ref: '#/components/schemas/Post'
put:
x-mojo-to: posts#update
security:
- Bearer: ['member', 'regp']
tags:
- posts
summary: "Uprava zpravu"
operationId: updatePost
requestBody:
content:
application/json:
schema:
type: object
properties:
state:
type: integer
enum: [0, 1, 2, 3, 4]
content:
type: string
isr_archived:
type: boolean
responses:
204:
description: Post updated
/posts/{id}/like:
patch:
security:
- Bearer: ['member', 'regp']
x-mojo-to: posts#ranking
tags:
- posts
summary: "Like"
operationId: likePost
parameters:
- name: ranking
in: query
required: false
schema:
type: integer
enum: [1]
default: 1
responses:
204:
description: Post liked
/posts/{id}/dislike:
patch:
security:
- Bearer: ['member', 'regp']
x-mojo-to: posts#ranking
tags:
- posts
summary: "Like"
operationId: dislikePost
parameters:
- name: ranking
in: query
required: false
schema:
type: integer
enum: [-1]
default: -1
responses:
204:
description: Post disliked
...@@ -26,6 +26,7 @@ create table "announcements" ( ...@@ -26,6 +26,7 @@ create table "announcements" (
create view "posts_view" as create view "posts_view" as
select select
"posts".*, "posts".*,
"posts"."ranking_likes" - "posts"."ranking_dislikes" as "ranking_score",
"users"."name" as "user_name", "users"."name" as "user_name",
"users"."main_group_name" as "group_name" "users"."main_group_name" as "group_name"
from posts from posts
......
create table "posts_history" (
"id" integer not null default nextval('uid_seq'),
"datetime" timestamp(0) not null default now(),
"post_id" integer not null,
"user_id" integer not null,
"content" text,
primary key("id")
);
create table "posts_ranking" (
"id" integer not null default nextval('uid_seq'),
"post_id" integer not null,
"user_id" integer not null,
"ranking" integer,
primary key("id"),
unique("post_id", "user_id")
);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment