corpshort 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +83 -0
- data/LICENSE.txt +21 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/app/public/style.css +320 -0
- data/app/views/edit.erb +26 -0
- data/app/views/index.erb +24 -0
- data/app/views/layout.erb +40 -0
- data/app/views/list.erb +11 -0
- data/app/views/show.erb +91 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config.ru +40 -0
- data/corpshort.gemspec +39 -0
- data/lib/corpshort.rb +7 -0
- data/lib/corpshort/app.rb +349 -0
- data/lib/corpshort/backends/base.rb +34 -0
- data/lib/corpshort/backends/dynamodb.rb +114 -0
- data/lib/corpshort/backends/memory.rb +61 -0
- data/lib/corpshort/backends/redis.rb +121 -0
- data/lib/corpshort/horizontal_pdf.rb +105 -0
- data/lib/corpshort/link.rb +83 -0
- data/lib/corpshort/version.rb +3 -0
- data/lib/corpshort/vertical_pdf.rb +107 -0
- metadata +240 -0
data/app/views/edit.erb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
<section>
|
2
|
+
<h2>Edit: <%= @link.name %></h2>
|
3
|
+
|
4
|
+
<form action='<%= update_path(@link) %>' method='POST'>
|
5
|
+
<input type='hidden' name='_method' value='PUT'>
|
6
|
+
<div class='form-row'>
|
7
|
+
<label for='edit_new_name'>Name</label>
|
8
|
+
<input type='text' id='edit_new_name' name='new_name' placeholder='short-name' value="<%= @link.name %>">
|
9
|
+
</div>
|
10
|
+
<div class='form-row'>
|
11
|
+
<label for='edit_url'>URL</label>
|
12
|
+
<input type='text' id='edit_url' name='url' placeholder='https://...' value="<%= @link.url %>">
|
13
|
+
</div>
|
14
|
+
<input type='submit' value='Save'>
|
15
|
+
</form>
|
16
|
+
|
17
|
+
<p>Entering a new name will create an another link for the URL. To rename completely, manually delete the old link; Be careful with renaming existing links.</p>
|
18
|
+
|
19
|
+
<hr>
|
20
|
+
|
21
|
+
<form action='<%= update_path(@link) %>' method='POST'>
|
22
|
+
<input type='hidden' name='_method' value='DELETE'>
|
23
|
+
<p><input type='submit' onclick="return confirm('Are you sure?')" class='btn-danger' value='Delete'></p>
|
24
|
+
</form>
|
25
|
+
</section>
|
26
|
+
|
data/app/views/index.erb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
<section class='new-link'>
|
2
|
+
<form action='/+/links' method='POST'>
|
3
|
+
<p><input type='text' name='url' placeholder='https://...' class='new-link-url'></p>
|
4
|
+
<p class='new-link-down'>⬇︎</p>
|
5
|
+
<p><input type='text' name='name' placeholder='Short name' class='new-link-name'></p>
|
6
|
+
<p><input type='submit' value='Shorten'></p>
|
7
|
+
</form>
|
8
|
+
</section>
|
9
|
+
|
10
|
+
<div class='infos'>
|
11
|
+
<% if notice_message %>
|
12
|
+
<section>
|
13
|
+
<h3>Notice</h3>
|
14
|
+
<%== notice_message %>
|
15
|
+
</section>
|
16
|
+
<% end %>
|
17
|
+
<section>
|
18
|
+
<h3>Usage</h3>
|
19
|
+
<form action='/+' method='GET'>
|
20
|
+
<p>Edit a link: <input type='text' name='show' placeholder='name...'> <input type='submit' value='Go'></p>
|
21
|
+
</form>
|
22
|
+
<p>Or add + (plus) sign after the URL like: <%= base_url %>/something → /something+</p>
|
23
|
+
</section>
|
24
|
+
</div>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset='utf-8'>
|
5
|
+
<meta name='viewport' content='width=device-width, minimum-scale=1'>
|
6
|
+
<title>Corpshort</title>
|
7
|
+
<link rel='stylesheet' href='/style.css' type="text/css">
|
8
|
+
</head>
|
9
|
+
|
10
|
+
<body>
|
11
|
+
<div class="container">
|
12
|
+
<header class="header">
|
13
|
+
<h1 class='logo'><a href="/">Corpshort</a></h1>
|
14
|
+
<nav>
|
15
|
+
<a href="/">Create</a>
|
16
|
+
<a href="/+/links">Recent</a>
|
17
|
+
</nav>
|
18
|
+
</header>
|
19
|
+
<% notice ||= session.delete(:notice); error ||= session.delete(:error) %>
|
20
|
+
<% if notice %>
|
21
|
+
<div class="notice"><%= notice %></div>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<% if error %>
|
25
|
+
<div class="error"><%= error %></div>
|
26
|
+
<% end %>
|
27
|
+
|
28
|
+
<div class="box">
|
29
|
+
<%== yield %>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<footer>
|
33
|
+
<div class="credit">
|
34
|
+
Powered by <a href="https://github.com/sorah/corpshort">sorah/corpshort</a>
|
35
|
+
</div>
|
36
|
+
</footer>
|
37
|
+
</div>
|
38
|
+
</body>
|
39
|
+
</html>
|
40
|
+
|
data/app/views/list.erb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
<h1><%= @title %></h1>
|
2
|
+
|
3
|
+
<ul>
|
4
|
+
<% @links.each do |name| %>
|
5
|
+
<li><a href='/<%= name %>+'><%= name %></li>
|
6
|
+
<% end %>
|
7
|
+
</ul>
|
8
|
+
|
9
|
+
<% if @next_token %>
|
10
|
+
<a href="<%= request.path %>?token=<%= URI.encode_www_form_component(@next_token) %>">More...</a>
|
11
|
+
<% end %>
|
data/app/views/show.erb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
<section class='link'>
|
2
|
+
<header>
|
3
|
+
<h2><a href="<%= short_link_url(@link) %>"><%= short_base_url.gsub(%r{\A.+://}, '') %>/<%= @link.name %></a></h2>
|
4
|
+
<a href='#' class='btn-link copy_btn'><input class='copy_btn_input' tabindex='-1' value="<%= short_link_url(@link) %>"><span class='copy_btn_text'>Copy</span></a>
|
5
|
+
<a href="<%= edit_path(@link) %>" class='btn-link'>Edit</a>
|
6
|
+
</header>
|
7
|
+
|
8
|
+
<p><a class='link-url' href="<%= @link.url %>"><%= @link.url %></a></p>
|
9
|
+
|
10
|
+
<div class='infos'>
|
11
|
+
<section>
|
12
|
+
<h3>Embed</h3>
|
13
|
+
<p>PDF is recommended for Keynote</p>
|
14
|
+
<div class='barcode-style'>
|
15
|
+
<div class='barcode-style-diagram barcode-style-diagram-cu-horizontal'>
|
16
|
+
<div class='barcode-style-diagram-code'></div>
|
17
|
+
<div class='barcode-style-diagram-url'></div>
|
18
|
+
</div>
|
19
|
+
<div class='barcode-style-links'>
|
20
|
+
<h4>Code + URL (Horizontal)</h4>
|
21
|
+
<ul>
|
22
|
+
<li><a href="<%= barcode_path(@link, 'horizontal', 'pdf', flex: true) %>">PDF</a></li>
|
23
|
+
<li><a href="<%= barcode_path(@link, 'horizontal', 'pdf') %>">PDF (Short)</a></li>
|
24
|
+
</ul>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
<div class='barcode-style'>
|
28
|
+
<div class='barcode-style-diagram barcode-style-diagram-cu-vertical'>
|
29
|
+
<div class='barcode-style-diagram-code'></div>
|
30
|
+
<div class='barcode-style-diagram-url'></div>
|
31
|
+
</div>
|
32
|
+
<div class='barcode-style-links'>
|
33
|
+
<h4>Code + URL (Vertical)</h4>
|
34
|
+
<ul>
|
35
|
+
<li><a href="<%= barcode_path(@link, 'vertical', 'pdf', flex: true) %>">PDF</a></li>
|
36
|
+
<li><a href="<%= barcode_path(@link, 'vertical', 'pdf') %>">PDF (Short)</a></li>
|
37
|
+
</ul>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
<div class='barcode-style'>
|
41
|
+
<div class='barcode-style-diagram barcode-style-diagram-codeonly'>
|
42
|
+
<div class='barcode-style-diagram-code'></div>
|
43
|
+
</div>
|
44
|
+
<div class='barcode-style-links'>
|
45
|
+
<h4>Code Only</h4>
|
46
|
+
<small>Code + URL is recommended</small>
|
47
|
+
<ul>
|
48
|
+
<li><a href="<%= barcode_path(@link, 'small', 'pdf') %>">PDF</a></li>
|
49
|
+
<li><a href="<%= barcode_path(@link, 'small', 'svg') %>">SVG</a></li>
|
50
|
+
<li><a href="<%= barcode_path(@link, 'small', 'png') %>">PNG</a></li>
|
51
|
+
</ul>
|
52
|
+
</div>
|
53
|
+
</div>
|
54
|
+
</section>
|
55
|
+
<section>
|
56
|
+
<h3>Metadata</h3>
|
57
|
+
<ul>
|
58
|
+
<li>Updated at: <%= @link.updated_at.utc.iso8601 %></li>
|
59
|
+
</ul>
|
60
|
+
</section>
|
61
|
+
<section>
|
62
|
+
<h3>Actions</h3>
|
63
|
+
<nav>
|
64
|
+
<p><a href="<%= urls_path(@link.url) %>">List all links for this URL</a></p>
|
65
|
+
</nav>
|
66
|
+
</section>
|
67
|
+
|
68
|
+
<script type='text/javascript'>
|
69
|
+
"use strict";
|
70
|
+
document.addEventListener("DOMContentLoaded", function() {
|
71
|
+
const copySupported = document.queryCommandSupported("copy");
|
72
|
+
document.querySelectorAll(".copy_btn").forEach(function(elem) {
|
73
|
+
if (!copySupported) {
|
74
|
+
elem.className += ' hidden';
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
elem.addEventListener("click", function(e) {
|
78
|
+
const value = e.currentTarget.querySelector(".copy_btn_input");
|
79
|
+
value.focus();
|
80
|
+
value.select();
|
81
|
+
document.execCommand("copy");
|
82
|
+
e.currentTarget.querySelector(".copy_btn_text").innerHTML = "Copied!";
|
83
|
+
e.preventDefault();
|
84
|
+
});
|
85
|
+
elem.addEventListener("mouseleave", function(e) {
|
86
|
+
e.currentTarget.querySelector(".copy_btn_text").innerHTML = "Copy";
|
87
|
+
});
|
88
|
+
});
|
89
|
+
});
|
90
|
+
</script>
|
91
|
+
</section>
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "corpshort"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/config.ru
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
require 'corpshort'
|
5
|
+
|
6
|
+
if ENV['RACK_ENV'] == 'production'
|
7
|
+
raise 'Set $SECRET_KEY_BASE' unless ENV['SECRET_KEY_BASE']
|
8
|
+
end
|
9
|
+
|
10
|
+
config = {
|
11
|
+
base_url: ENV['CORPSHORT_BASE_URL'],
|
12
|
+
short_base_url: ENV['CORPSHORT_SHORT_BASE_URL'],
|
13
|
+
}
|
14
|
+
|
15
|
+
case ENV.fetch('CORPSHORT_BACKEND', 'redis')
|
16
|
+
when 'redis'
|
17
|
+
require 'corpshort/backends/redis'
|
18
|
+
config[:backend] = Corpshort::Backends::Redis.new(
|
19
|
+
redis: ENV.key?('REDIS_URL') ? lambda { Redis.new(url: ENV['REDIS_URL']) } : Redis.method(:current),
|
20
|
+
prefix: ENV.fetch('CORPSHORT_REDIS_PREFIX', 'corpshort:'),
|
21
|
+
)
|
22
|
+
when 'dynamodb'
|
23
|
+
require 'corpshort/backends/dynamodb'
|
24
|
+
config[:backend] = Corpshort::Backends::Dynamodb.new(
|
25
|
+
region: ENV.fetch('CORPSHORT_DYNAMODB_REGION'),
|
26
|
+
table: ENV.fetch('CORPSHORT_DYNAMODB_TABLE'),
|
27
|
+
)
|
28
|
+
else
|
29
|
+
raise ArgumentError, "Unsupported $CORPSHORT_BACKEND"
|
30
|
+
end
|
31
|
+
|
32
|
+
use(
|
33
|
+
Rack::Session::Cookie,
|
34
|
+
key: 'corpshortsess',
|
35
|
+
expire_after: 86400,
|
36
|
+
secure: ENV.fetch('CORPSHORT_SECURE_SESSION', ENV['RACK_ENV'] == 'production' ? '1' : nil) == '1',
|
37
|
+
secret: ENV.fetch('SECRET_KEY_BASE', SecureRandom.base64(256)),
|
38
|
+
)
|
39
|
+
|
40
|
+
run Corpshort.app(config)
|
data/corpshort.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "corpshort/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "corpshort"
|
8
|
+
spec.version = Corpshort::VERSION
|
9
|
+
spec.authors = ["Sorah Fukumori"]
|
10
|
+
spec.email = ["sorah@cookpad.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{"go/" like private link shortener for internal purpose}
|
13
|
+
spec.homepage = "https://github.com/sorah/corpshort"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency "sinatra"
|
24
|
+
spec.add_dependency "rack-protection"
|
25
|
+
|
26
|
+
spec.add_dependency "erubi"
|
27
|
+
|
28
|
+
spec.add_dependency "redis"
|
29
|
+
spec.add_dependency "aws-sdk-dynamodb"
|
30
|
+
|
31
|
+
spec.add_dependency "rqrcode"
|
32
|
+
spec.add_dependency "prawn"
|
33
|
+
spec.add_dependency "prawn-qrcode"
|
34
|
+
|
35
|
+
spec.add_development_dependency "bundler"
|
36
|
+
spec.add_development_dependency "rake"
|
37
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
38
|
+
spec.add_development_dependency "rack-test"
|
39
|
+
end
|
data/lib/corpshort.rb
ADDED
@@ -0,0 +1,349 @@
|
|
1
|
+
require 'rqrcode'
|
2
|
+
require 'prawn'
|
3
|
+
require 'prawn/qrcode'
|
4
|
+
|
5
|
+
require 'json'
|
6
|
+
require 'erubi'
|
7
|
+
require 'sinatra/base'
|
8
|
+
require 'rack/protection'
|
9
|
+
|
10
|
+
require 'corpshort/link'
|
11
|
+
require 'corpshort/vertical_pdf'
|
12
|
+
require 'corpshort/horizontal_pdf'
|
13
|
+
|
14
|
+
require 'uri'
|
15
|
+
|
16
|
+
module Corpshort
|
17
|
+
def self.app(*args)
|
18
|
+
App.rack(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
class App < Sinatra::Base
|
22
|
+
CONTEXT_RACK_ENV_NAME = 'corpshort.ctx'
|
23
|
+
|
24
|
+
def self.initialize_context(config)
|
25
|
+
{
|
26
|
+
config: config,
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.rack(config={})
|
31
|
+
klass = App
|
32
|
+
|
33
|
+
test = config[:test]
|
34
|
+
session = {}
|
35
|
+
context = initialize_context(config)
|
36
|
+
lambda { |env|
|
37
|
+
env['rack.session'] = session if test # FIXME:
|
38
|
+
env[CONTEXT_RACK_ENV_NAME] = context
|
39
|
+
klass.call(env)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
configure do
|
44
|
+
enable :logging
|
45
|
+
end
|
46
|
+
|
47
|
+
set :root, File.expand_path(File.join(__dir__, '..', '..', 'app'))
|
48
|
+
set :erb, :escape_html => true
|
49
|
+
|
50
|
+
use Rack::Protection::FrameOptions
|
51
|
+
use Rack::Protection::HttpOrigin
|
52
|
+
use Rack::Protection::IPSpoofing
|
53
|
+
use Rack::Protection::JsonCsrf
|
54
|
+
use Rack::Protection::PathTraversal
|
55
|
+
use Rack::Protection::RemoteToken, only_if: -> (env) { ! env['PATH_INFO'].start_with?('/+api') }
|
56
|
+
use Rack::Protection::SessionHijacking
|
57
|
+
use Rack::Protection::XSSHeader
|
58
|
+
|
59
|
+
use Rack::MethodOverride
|
60
|
+
|
61
|
+
helpers do
|
62
|
+
include Prawn::Measurements
|
63
|
+
|
64
|
+
def context
|
65
|
+
request.env[CONTEXT_RACK_ENV_NAME]
|
66
|
+
end
|
67
|
+
|
68
|
+
def conf
|
69
|
+
context.fetch(:config)
|
70
|
+
end
|
71
|
+
|
72
|
+
def notice_message
|
73
|
+
conf[:notice_message]
|
74
|
+
end
|
75
|
+
|
76
|
+
def base_url
|
77
|
+
conf[:base_url] || request.base_url
|
78
|
+
end
|
79
|
+
|
80
|
+
def short_base_url
|
81
|
+
conf[:short_base_url] || base_url
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def backend
|
86
|
+
@backend ||= conf.fetch(:backend)
|
87
|
+
end
|
88
|
+
|
89
|
+
def link_name(name = params[:name])
|
90
|
+
name.tr('_', '-')
|
91
|
+
end
|
92
|
+
|
93
|
+
def short_link_url(link, **kwargs)
|
94
|
+
link_url(link, base_url: short_base_url, **kwargs)
|
95
|
+
end
|
96
|
+
|
97
|
+
def link_url(link, protocol: true, base_url: self.base_url())
|
98
|
+
name = link.is_a?(String) ? link_name(link) : link.name
|
99
|
+
"#{base_url}/#{name}".yield_self do |url|
|
100
|
+
if protocol
|
101
|
+
url
|
102
|
+
else
|
103
|
+
url.gsub(/\Ahttps?:\/\//, '')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def edit_path(link)
|
109
|
+
"/+/links/#{URI.encode_www_form_component(link.name)}/edit"
|
110
|
+
end
|
111
|
+
|
112
|
+
def update_path(link)
|
113
|
+
"/+/links/#{URI.encode_www_form_component(link.name)}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def urls_path(url)
|
117
|
+
"/+/urls/#{url}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def barcode_path(link, kind, ext, flex: nil)
|
121
|
+
"/+/links/#{URI.encode_www_form_component(link.name)}/#{kind}.#{ext}#{flex.nil? ? nil : "?flex=#{flex}"}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
get '/' do
|
126
|
+
erb :index
|
127
|
+
end
|
128
|
+
|
129
|
+
## Pages
|
130
|
+
|
131
|
+
get '/+' do
|
132
|
+
if params[:show]
|
133
|
+
redirect "/+/links/#{link_name(params[:show])}"
|
134
|
+
end
|
135
|
+
halt 404
|
136
|
+
end
|
137
|
+
|
138
|
+
post '/+/links' do
|
139
|
+
unless params[:name] && params[:url]
|
140
|
+
session[:error] = "Name and URL are required"
|
141
|
+
redirect '/'
|
142
|
+
end
|
143
|
+
|
144
|
+
begin
|
145
|
+
link = Link.new({name: link_name, url: params[:url]})
|
146
|
+
link.save!(backend, create_only: true)
|
147
|
+
rescue Corpshort::Link::ValidationError, Corpshort::Backends::Base::ConflictError
|
148
|
+
session[:error] = $!.message
|
149
|
+
redirect '/'
|
150
|
+
end
|
151
|
+
|
152
|
+
redirect "/#{link.name}+"
|
153
|
+
end
|
154
|
+
|
155
|
+
get '/+/links' do
|
156
|
+
@links, @next_token = backend.list_links(token: params[:token])
|
157
|
+
@title = "Recent links"
|
158
|
+
erb :list
|
159
|
+
end
|
160
|
+
|
161
|
+
get '/+/links/*name/small.svg' do
|
162
|
+
@link = backend.get_link(params[:name])
|
163
|
+
halt 404, "not found" unless @link
|
164
|
+
|
165
|
+
content_type :svg
|
166
|
+
RQRCode::QRCode.new(link_url(@link), level: :m).as_svg(module_size: 6)
|
167
|
+
end
|
168
|
+
get '/+/links/*name/small.png' do
|
169
|
+
@link = backend.get_link(params[:name])
|
170
|
+
|
171
|
+
halt 404, "not found" unless @link
|
172
|
+
content_type :png
|
173
|
+
RQRCode::QRCode.new(link_url(@link), level: :m).as_png(size: 120).to_datastream.to_s
|
174
|
+
end
|
175
|
+
get '/+/links/*name/small.pdf' do
|
176
|
+
@link = backend.get_link(params[:name])
|
177
|
+
halt 404, "not found" unless @link
|
178
|
+
|
179
|
+
content_type :pdf
|
180
|
+
Prawn::Document.new(page_size: [cm2pt(2), cm2pt(2)], margin: 0) do |pdf|
|
181
|
+
pdf.fill_color 'FFFFFF'
|
182
|
+
pdf.fill { pdf.rounded_rectangle [cm2pt(2), cm2pt(2)], cm2pt(2), cm2pt(2), 10 }
|
183
|
+
pdf.print_qr_code(link_url(@link), level: :m, extent: cm2pt(2), stroke: false)
|
184
|
+
end.render
|
185
|
+
end
|
186
|
+
|
187
|
+
get '/+/links/*name/vertical.pdf' do
|
188
|
+
@link = backend.get_link(params[:name])
|
189
|
+
halt 404, "not found" unless @link
|
190
|
+
|
191
|
+
content_type :pdf
|
192
|
+
|
193
|
+
VerticalPdf.new(
|
194
|
+
url: link_url(@link),
|
195
|
+
base_url: short_base_url.sub(%r{\A.+://}, ''),
|
196
|
+
name: @link.name,
|
197
|
+
flex: params[:flex],
|
198
|
+
).document.render
|
199
|
+
end
|
200
|
+
|
201
|
+
get '/+/links/*name/horizontal.pdf' do
|
202
|
+
@link = backend.get_link(params[:name])
|
203
|
+
halt 404, "not found" unless @link
|
204
|
+
|
205
|
+
content_type :pdf
|
206
|
+
HorizontalPdf.new(
|
207
|
+
url: link_url(@link),
|
208
|
+
base_url: short_base_url.sub(%r{\A.+://}, ''),
|
209
|
+
name: @link.name,
|
210
|
+
flex: params[:flex],
|
211
|
+
).document.render
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
get '/+/links/*name/edit' do
|
216
|
+
@link = backend.get_link(params[:name])
|
217
|
+
if @link
|
218
|
+
erb :edit
|
219
|
+
else
|
220
|
+
halt 404, "not found"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
get '/+/links/*name' do
|
225
|
+
redirect "/#{params[:name]}+"
|
226
|
+
end
|
227
|
+
|
228
|
+
put '/+/links/*name' do
|
229
|
+
@link = backend.get_link(params[:name])
|
230
|
+
halt 404, "not found" unless @link
|
231
|
+
|
232
|
+
@link.url = params[:url] if params[:url]
|
233
|
+
|
234
|
+
rename = params[:new_name] && @link.name != params[:new_name]
|
235
|
+
if rename
|
236
|
+
new_name = link_name(params[:new_name])
|
237
|
+
@link = Link.new(name: new_name, url: @link.url)
|
238
|
+
# Link.validate_name(new_name)
|
239
|
+
# backend.rename_link(@link, new_name)
|
240
|
+
end
|
241
|
+
|
242
|
+
begin
|
243
|
+
@link.save!(backend, create_only: rename)
|
244
|
+
rescue Corpshort::Link::ValidationError, Corpshort::Backends::Base::ConflictError
|
245
|
+
session[:error] = $!.message
|
246
|
+
redirect "/+/links/#{@link.name}/edit"
|
247
|
+
end
|
248
|
+
|
249
|
+
redirect "/#{@link.name}+"
|
250
|
+
end
|
251
|
+
|
252
|
+
delete '/+/links/*name' do
|
253
|
+
backend.delete_link(params[:name])
|
254
|
+
redirect "/"
|
255
|
+
end
|
256
|
+
|
257
|
+
get '/+/urls/*url' do
|
258
|
+
url = env['REQUEST_URI'][8..-1]
|
259
|
+
@links, @next_token = backend.list_links_by_url(url), nil
|
260
|
+
@title = "Links for URL #{url}"
|
261
|
+
erb :list
|
262
|
+
end
|
263
|
+
|
264
|
+
## API
|
265
|
+
|
266
|
+
get '/+api/links' do
|
267
|
+
content_type :json
|
268
|
+
links, next_token = backend.list_links(token: params[:token])
|
269
|
+
{links: links, next_token: next_token}.to_json
|
270
|
+
end
|
271
|
+
|
272
|
+
post '/+api/links' do
|
273
|
+
content_type :json
|
274
|
+
|
275
|
+
unless params[:name] && params[:url]
|
276
|
+
halt 400, '{"error": "missing_params", "error_message": "name and url are required"}'
|
277
|
+
end
|
278
|
+
|
279
|
+
begin
|
280
|
+
link = Link.new({name: link_name, url: params[:url]})
|
281
|
+
link.save!(backend, create_only: true)
|
282
|
+
rescue Corpshort::Link::ValidationError => e
|
283
|
+
halt(400, {error: :validation_error, error_message: e.message}.to_json)
|
284
|
+
rescue Corpshort::Backends::Base::ConflictError
|
285
|
+
halt(409, {error: :conflict, error_message: e.message}.to_json)
|
286
|
+
end
|
287
|
+
|
288
|
+
link.to_json
|
289
|
+
end
|
290
|
+
|
291
|
+
get '/+api/links/*name' do
|
292
|
+
content_type :json
|
293
|
+
link = backend.get_link(link_name)
|
294
|
+
halt 404, '{"error": "not_found"}' unless link
|
295
|
+
link.to_json
|
296
|
+
end
|
297
|
+
|
298
|
+
put '/+api/links/*name' do
|
299
|
+
content_type :json
|
300
|
+
link = backend.get_link(link_name)
|
301
|
+
halt 404, '{"error": "not_found"}' unless link
|
302
|
+
link.url = params[:url] if params[:url]
|
303
|
+
|
304
|
+
begin
|
305
|
+
link.save!(backend)
|
306
|
+
rescue Corpshort::Link::ValidationError => e
|
307
|
+
halt(400, {error: :validation_error, error_message: e.message}.to_json)
|
308
|
+
rescue Corpshort::Backends::Base::ConflictError
|
309
|
+
halt(409, {error: :conflict, error_message: e.message}.to_json)
|
310
|
+
end
|
311
|
+
link.to_json
|
312
|
+
end
|
313
|
+
|
314
|
+
delete '/+api/links/*name' do
|
315
|
+
backend.delete_link(link_name)
|
316
|
+
status 202
|
317
|
+
""
|
318
|
+
end
|
319
|
+
|
320
|
+
get '/+api/urls/*url' do
|
321
|
+
content_type :json
|
322
|
+
url = env['REQUEST_URI'][8..-1]
|
323
|
+
links, next_token = backend.list_links_by_url(url), nil
|
324
|
+
{links: links, next_token: next_token}.to_json
|
325
|
+
end
|
326
|
+
|
327
|
+
## Shortlink
|
328
|
+
|
329
|
+
get '/*name' do
|
330
|
+
name = params[:name]
|
331
|
+
show = name.end_with?('+')
|
332
|
+
if show
|
333
|
+
name = name[0..-2]
|
334
|
+
end
|
335
|
+
|
336
|
+
@link = backend.get_link(link_name(name))
|
337
|
+
|
338
|
+
unless @link
|
339
|
+
halt 404, 'not found'
|
340
|
+
end
|
341
|
+
|
342
|
+
if show
|
343
|
+
erb :show
|
344
|
+
else
|
345
|
+
redirect @link.url
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|