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

Verze s podporou editace

parent 6f1c5f87
No related branches found
No related tags found
No related merge requests found
0.2.0 1.0.0
...@@ -100,6 +100,8 @@ sub startup { ...@@ -100,6 +100,8 @@ sub startup {
$r->get('/')->to(cb => sub { shift->render('index'); }); $r->get('/')->to(cb => sub { shift->render('index'); });
$r->post('/')->to('Shortcut#create'); $r->post('/')->to('Shortcut#create');
$r->get('/shortcuts')->to(cb => sub { shift->render('shortcuts'); });
$r->get('/login')->to('OIDC#callback'); $r->get('/login')->to('OIDC#callback');
$r->get('/logout')->to('OIDC#do_logout'); $r->get('/logout')->to('OIDC#do_logout');
......
...@@ -4,7 +4,22 @@ use Mojo::Base 'Mojolicious::Controller', -signatures; ...@@ -4,7 +4,22 @@ use Mojo::Base 'Mojolicious::Controller', -signatures;
use Data::Validate::URI qw(is_uri); use Data::Validate::URI qw(is_uri);
use Image::PNG::QRCode 'qrpng'; use Image::PNG::QRCode 'qrpng';
# This action will render a template sub redirect ($c) {
my $shortcut = $c->schema->resultset('Shortcut')->search({
shortcut => $c->stash->{shortcut},
is_active => 1,
deleted => undef,
})->first;
if ( ! $shortcut ) {
$c->render( status => 404, text => 'not found' );
return;
}
$c->res->code($shortcut->code);
$c->redirect_to($shortcut->url);
}
sub create ($c) { sub create ($c) {
my $url = $c->param('url'); my $url = $c->param('url');
...@@ -34,39 +49,24 @@ sub create ($c) { ...@@ -34,39 +49,24 @@ sub create ($c) {
} }
my %data = ( my %data = (
user_id => $c->current_user->{id},
deleted => undef, deleted => undef,
url => $url, url => $url,
); );
my $shortcut = $c->schema->resultset('Shortcut')->search(\%data)->first; my $shortcut = $c->current_user->shortcuts(\%data)->first;
$shortcut ||= $c->schema->resultset('Shortcut')->create({
%data,
shortcut => $custom // $c->schema->resultset('Shortcut')->generate(),
});
$url = 'https://' . $c->config->{domain} . '/' . $shortcut->shortcut;
$c->render('shortcut/created', url => $url, shortcut => $shortcut );
}
sub redirect ($c) {
my $shortcut = $c->schema->resultset('Shortcut')->search({
shortcut => $c->stash->{shortcut},
is_active => 1,
deleted => undef,
})->first;
if ( ! $shortcut ) { if ( ! $shortcut ) {
$c->render( status => 404, text => 'not found' ); $data{shortcut} = $custom || $c->schema->resultset('Shortcut')->generate();
return; $shortcut = $c->current_user->add_to_shortcuts(\%data);
} }
$c->res->code($shortcut->code); $c->render('shortcut/created',
$c->redirect_to($shortcut->url); url => 'https://' . $c->config->{domain} . '/' . $shortcut->shortcut,
shortcut => $shortcut
);
} }
sub qr ($c) { sub qr ($c) {
my $url = 'https://' . $c->config->{domain} . '/' . $c->stash->{shortcut}; my $url = 'https://' . $c->config->{domain} . '/' . $c->stash->{shortcut};
my $png = qrpng (text => $url, level => 4); my $png = qrpng (text => $url, level => 4);
...@@ -79,13 +79,50 @@ sub list ($c) { ...@@ -79,13 +79,50 @@ sub list ($c) {
my @shortcuts = (); my @shortcuts = ();
SHORTCUT: SHORTCUT:
foreach my $shortcut ( $c->stash->{user}->shortcuts ) { foreach my $shortcut ( $c->stash->{user}->shortcuts({ deleted => undef }) ) {
push @shortcuts, $c->spec_filter( push @shortcuts, $c->spec_filter(
{ $shortcut->get_columns }, 'Shortcut' { $shortcut->get_columns }, 'Shortcut'
); );
} }
$c->render(json => \@shortcuts ); $c->render(json => \@shortcuts, );
}
sub update ($c) {
my $shortcut = $c->stash->{user}->shortcuts({
id => $c->stash->{id}
})->first;
if ( ! $shortcut ) {
$c->render( status => 404, text => 'not found' );
return;
}
$shortcut->update({
code => $c->req->json->{code},
url => $c->req->json->{url},
});
$c->render(status => 204, text => '' );
}
sub delete ($c) {
my $shortcut = $c->stash->{user}->shortcuts({
id => $c->stash->{id}
})->first;
if ( ! $shortcut ) {
$c->render( status => 404, text => 'not found' );
return;
}
$shortcut->update({
deleted => \'now()'
});
$c->render(status => 204, text => '' );
} }
1; 1;
...@@ -3,7 +3,7 @@ openapi: 3.0.3 ...@@ -3,7 +3,7 @@ openapi: 3.0.3
info: info:
title: Piratský zkracovač title: Piratský zkracovač
description: Piratský zkracovač API description: Piratský zkracovač API
version: 1.15.1 version: 1.0.0
license: license:
name: Artistic License 2.0 name: Artistic License 2.0
url: https://www.perlfoundation.org/artistic-license-20.html url: https://www.perlfoundation.org/artistic-license-20.html
...@@ -25,15 +25,19 @@ components: ...@@ -25,15 +25,19 @@ components:
properties: properties:
id: id:
type: integer type: integer
readOnly: true
maxLength: 6
shortcut: shortcut:
type: string type: string
description: Zkratka description: Zkratka
url: url:
type: string type: string
description: URL pro přesměrování description: URL pro přesměrování
maxLength: 1024
code: code:
type: integer type: integer
description: Kód přesměrování description: Kód přesměrování
enum: [301, 302]
securitySchemes: securitySchemes:
Token: Token:
type: apiKey type: apiKey
...@@ -42,6 +46,23 @@ components: ...@@ -42,6 +46,23 @@ components:
paths: paths:
/shortcuts: /shortcuts:
post:
tags:
- shortcuts
security:
- Token: []
summary: "Pridat zkratku"
operationId: ShortcutCreate
x-mojo-to: shortcut#create1
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Shortcut'
responses:
201:
description: Shortcut created
get: get:
tags: tags:
- shortcuts - shortcuts
...@@ -60,3 +81,32 @@ paths: ...@@ -60,3 +81,32 @@ paths:
items: items:
$ref: '#/components/schemas/Shortcut' $ref: '#/components/schemas/Shortcut'
/shortcuts/{id}:
put:
tags:
- shortcuts
security:
- Token: []
summary: "Aktualizovat zkratku"
operationId: ShortcutUpdate
x-mojo-to: shortcut#update
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Shortcut'
responses:
204:
description: Shortcut updated
delete:
tags:
- shortcuts
security:
- Token: []
summary: "Smazat zkratku"
operationId: ShortcutDelete
x-mojo-to: shortcut#delete
responses:
204:
description: Shortcut deleted
.jsgrid-edit-row > .jsgrid-cell, .content-block table tr:nth-child(2n).jsgrid-edit-row .jsgrid-cell, .table--striped tr:nth-child(2n).jsgrid-edit-row .jsgrid-cell {
background: #92c6ab;
}
.jsgrid-edit-row input, .jsgrid-edit-row select, .jsgrid-edit-row textarea, .jsgrid-filter-row input, .jsgrid-filter-row select, .jsgrid-filter-row textarea, .jsgrid-insert-row input, .jsgrid-insert-row select, .jsgrid-insert-row textarea {
width: 100%;
padding: 0;
}
This diff is collapsed.
...@@ -39,3 +39,8 @@ create table "log" ( ...@@ -39,3 +39,8 @@ create table "log" (
primary key("id"), primary key("id"),
foreign key("shortcut_id") references "shortcuts" ("id") on update cascade on delete restrict foreign key("shortcut_id") references "shortcuts" ("id") on update cascade on delete restrict
); );
-- 2 up
alter table "shortcuts" alter column "code" set default 302;
alter table "shortcuts" drop constraint "shortcuts_shortcut_key";
create index "shortcuts_shortcut_idx" on "shortcuts" ("shortcut");
...@@ -16,10 +16,6 @@ Pro použiti Pirátského zkracovače musíte se přihliásit <strong><a href="< ...@@ -16,10 +16,6 @@ Pro použiti Pirátského zkracovače musíte se přihliásit <strong><a href="<
% if ( $c->is_user_authenticated ) { % if ( $c->is_user_authenticated ) {
<div class="content-block"> <div class="content-block">
%= include 'includes/form'; %= include 'shortcut/form';
% if ( $c->current_user->shortcuts({ deleted => undef })->count() ) {
%= include 'shortcut/list';
% }
</div> </div>
% } % }
...@@ -23,11 +23,13 @@ ...@@ -23,11 +23,13 @@
<link rel="manifest" href="/manifest.json"/> <link rel="manifest" href="/manifest.json"/>
<link rel="stylesheet" href="<%= $c->config->{styleguide} %>css/styles.css"/> <link rel="stylesheet" href="<%= $c->config->{styleguide} %>css/styles.css"/>
<script src="https://cdn-unpkg.pirati.cz/jquery@3.6.0/dist/jquery.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script src="https://cdn-unpkg.pirati.cz/jquery@3.6.0/dist/jquery.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<!-- <link href="https://cdn-unpkg.pirati.cz/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">-->
<script src="https://cdn-unpkg.pirati.cz/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
<script src="https://cdn-unpkg.pirati.cz/htmx.org@1.7.0" integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" crossorigin="anonymous"></script> <script src="https://cdn-unpkg.pirati.cz/htmx.org@1.7.0" integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" crossorigin="anonymous"></script>
<!-- <link rel="stylesheet" href="https://cdn-unpkg.pirati.cz/bootstrap-table@1.20.2/dist/bootstrap-table.min.css">-->
<script src="https://cdn-unpkg.pirati.cz/bootstrap-table@1.20.2/dist/bootstrap-table.min.js"></script> <link type="text/css" rel="stylesheet" href="https://cdn-cdnjs-cloudflare.pirati.cz/ajax/libs/jsgrid/1.5.3/jsgrid.min.css" />
<link type="text/css" rel="stylesheet" href="https://cdn-cdnjs-cloudflare.pirati.cz/ajax/libs/jsgrid/1.5.3/jsgrid-theme.min.css" />
<script type="text/javascript" src="https://cdn-cdnjs-cloudflare.pirati.cz/ajax/libs/jsgrid/1.5.3/jsgrid.min.js"></script>
<link rel="stylesheet" href="/custom.css"/>
</head> </head>
<body> <body>
...@@ -49,11 +51,21 @@ ...@@ -49,11 +51,21 @@
</div> </div>
<div v-if="show || isLgScreenSize" class="navbar__main navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto flex items-center"> <div v-if="show || isLgScreenSize" class="navbar__main navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto flex items-center">
<div class="flex-grow"> <div class="flex-grow">
<!-- MENU -->
% if ( $c->is_user_authenticated ) {
<ul class="navbar-menu text-white">
<li class="navbar-menu__item">
<a href="/" class="navbar-menu__link">Nová zkratka</a>
</li>
<li class="navbar-menu__item">
<a href="/shortcuts" class="navbar-menu__link">Moje zkratky</a>
</li>
</ul>
% }
</div> </div>
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
% if ( $c->is_user_authenticated ) { % if ( $c->is_user_authenticated ) {
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<span class="head-heavy-2xs"><%= $c->current_user->displayname %></span> <span class="head-heavy-2xs"><%= $c->current_user->displayname %></span>
<div class="avatar avatar--2xs"> <div class="avatar avatar--2xs">
...@@ -61,7 +73,6 @@ ...@@ -61,7 +73,6 @@
</div> </div>
<a href="/logout"><button class="text-grey-200 hover:text-white"><i class="ico--log-out"></i></button></a> <a href="/logout"><button class="text-grey-200 hover:text-white"><i class="ico--log-out"></i></button></a>
</div> </div>
% } else { % } else {
<a href="<%= $c->oidc->authorize %>"> <a href="<%= $c->oidc->authorize %>">
<button class="btn btn--icon btn--grey-125 btn--hoveractive"> <button class="btn btn--icon btn--grey-125 btn--hoveractive">
...@@ -82,7 +93,7 @@ ...@@ -82,7 +93,7 @@
</ui-app> </ui-app>
</nav> </nav>
<div class="container container--default py-8 lg:py-24"> <div class="container container--default py-8">
<section> <section>
<main> <main>
<%= content %> <%= content %>
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
<label class="form-field__label" for="url">URL</label> <label class="form-field__label" for="url">URL</label>
<div class="form-field__wrapper form-field__wrapper--shadowed"> <div class="form-field__wrapper form-field__wrapper--shadowed">
<input type="text" name="url" class="text-input form-field__control" value="" placeholder="https://www.pirati.cz/program/dlouhodoby/psychotropni-latky/" required="required" /> <input type="text" name="url" class="text-input form-field__control" value="" placeholder="https://www.pirati.cz/program/dlouhodoby/psychotropni-latky/" required="required" />
</div> </div>
</div> </div>
...@@ -16,7 +15,6 @@ ...@@ -16,7 +15,6 @@
<label class="form-field__label" for="shortcut">Zkratka</label> <label class="form-field__label" for="shortcut">Zkratka</label>
<div class="form-field__wrapper form-field__wrapper--shadowed"> <div class="form-field__wrapper form-field__wrapper--shadowed">
<input type="text" name="shortcut" class="text-input form-field__control w-40" size="8" maxlength="8" value="" placeholder="thc" /> <input type="text" name="shortcut" class="text-input form-field__control w-40" size="8" maxlength="8" value="" placeholder="thc" />
<button class="btn btn--grey-125 btn--hoveractive ml-4"> <button class="btn btn--grey-125 btn--hoveractive ml-4">
<div class="btn__body">Zkrátit</div> <div class="btn__body">Zkrátit</div>
</button> </button>
......
<div class="card elevation-4 space-y-4 mt-2">
<div class="card__body">
<h2>Moje zkratky</h2>
<table id="Shortcuts"
class="table table--bordered"
data-row-style="rowStyle"
data-unique-id="id"
data-url="/api/shortcuts" >
<thead>
<tr>
<th data-field="shortcut" data-width="10" data-width-unit="%" data-sortable="true">Zkratka</th>
<th data-field="url" data-width="90" data-width-unit="%" data-sortable="true">URL</th>
</tr>
</thead>
</table>
</div>
</div>
<script>
$('#Shortcuts').bootstrapTable({
ajaxOptions: {
headers: {
'X-Auth-Token': '<%= $c->current_user->token %>'
}
},
sortable: true,
pagination: true,
pageSize: 50,
paginationParts: ['pageInfo', 'pageList'],
sidePagination: 'server',
// onClickCell: function (field, value, row, $element) {
// window.location.href ='/archive/' + row.id;
// },
});
function rowStyle(row, index) {
return {
css: {
cursor: 'pointer'
}
}
}
</script>
% layout 'default';
<h1 class="head-alt-md md:head-alt-lg max-w-5xl mb-8">Moje zkratky</h1>
<div id="jsGrid"></div>
<script>
jsGrid.validators.url = {
message: 'Zadejte platný URL',
validator: function(value, item) {
let url;
try {
url = new URL(value);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
}
$(function() {
$("#jsGrid").jsGrid({
width: "100%",
height: "auto",
inserting: false,
editing: true,
sorting: true,
paging: true,
selecting: false,
autoload: true,
invalidMessage: 'Neplatná data',
deleteConfirm: 'Opravdu chcete smazat zkratku?',
controller: {
loadData: function(filter) {
return $.ajax({
url: "/api/shortcuts",
dataType: "json",
headers: { 'X-Auth-Token': '<%= $c->current_user->token %>' },
data: filter,
});
},
insertItem: function(item) {
return $.ajax({
type: "POST",
dataType: "json",
headers: { 'X-Auth-Token': '<%= $c->current_user->token %>' },
url: "/api/shortcuts",
data: JSON.stringify(item)
});
},
updateItem: function(item) {
return $.ajax({
type: "PUT",
dataType: "json",
headers: { 'X-Auth-Token': '<%= $c->current_user->token %>' },
url: "/api/shortcuts/" + item.id,
data: JSON.stringify(item)
});
},
deleteItem: function(item) {
return $.ajax({
type: "DELETE",
headers: { 'X-Auth-Token': '<%= $c->current_user->token %>' },
url: "/api/shortcuts/" + item.id,
});
},
},
fields: [
{ name: "shortcut", title: "Zkratka", type: "text", width: 50, editing: false },
{ name: "url", title: "URL", type: "text", width: 250, validate: "url" },
{ name: "code", title: "Týp přesměrování", type: "select", width: 70, items: [
{ name: "302 dočasné", code: 302 },
{ name: "301 trvalé", code: 301 },
],
valueField: "code",
textField: "name"
},
{ type: "control", editButton: false, width: 30 }
]
});
});
</script>
<button class="btn <%= $classes %>">
<div class="btn__body <%= $bodyClasses %>"><%= $cta %></div>
</button>
<footer class="border-t border-grey-125 pt-4 flex flex-col md:flex-row md:justify-between space-y-4">
<img src="<%= $c->config->{styleguide} %>images/logo-full-black.svg" class="w-32" alt="Pirátská strana" />
<p>Piráti, 2021. Všechna práva vyhlazena.</p>
</footer>
<a href="<%= $url %>">
<button class="btn btn--icon <%= $classes %>">
<div class="btn__body-wrap">
<div class="btn__body <%= $bodyClasses %>"><%= $cta %></div>
<div class="btn__icon <%= $iconClasses %>">
<i class="ico--<%= $icon %>"></i>
</div>
</div>
</button>
</a>
<nav class="navbar navbar--simple __js-root">
<ui-app inline-template>
<ui-navbar inline-template>
<div>
<div class="container container--wide navbar__content" :class="{'navbar__content--initialized': true}">
<div class="navbar__brand my-4 flex items-center lg:pr-8 lg:my-0">
<a href="#">
<img src="<%= $c->config->{styleguide} %>/images/logo-round-white.svg" class="w-8" />
</a>
<span class="pl-4 font-bold text-xl lg:border-r lg:border-grey-300 lg:pr-8"><%= $c->config->{name} %></span>
</div>
<div class="navbar__menutoggle my-4 flex justify-end lg:hidden">
<a href="#" @click="show = !show" class="no-underline hover:no-underline">
<i class="ico--menu text-3xl"></i>
</a>
</div>
<div v-if="show || isLgScreenSize" class="navbar__main navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto flex items-center">
<div class="flex-grow">
<!-- MENU -->
</div>
<div class="flex items-center space-x-4">
% if ( $c->is_user_authenticated ) {
<div class="flex items-center space-x-4">
<span class="head-heavy-2xs"><%= $c->current_user->{displayname} %></span>
<div class="avatar avatar--2xs">
<img src="<%= $c->config->{piratar}%><%= $c->current_user->{username} %>.jpg" alt="<%= $c->current_user->{displayname} %>" />
</div>
<a href="/logout"><button class="text-grey-200 hover:text-white"><i class="ico--log-out"></i></button></a>
</div>
% } else {
%= include 'styleguide/icon-button', classes => "btn--grey-125 btn--hoveractive", bodyClasses=>'', iconClasses=>'', icon => 'pirati', cta => "Přihlásit se", url => $c->oidc->authorize ;
% }
</div>
</div>
</div>
</div>
</ui-navbar>
</ui-app>
</nav>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment