morpho 0.1.0 → 0.1.1
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 +4 -4
- data/Rakefile +1 -1
- data/app/api/concerns/morpho/grape/http_responses.rb +41 -0
- data/app/api/concerns/morpho/grape/jwt_authentication.rb +77 -0
- data/app/api/concerns/morpho/grape/user_activation.rb +13 -0
- data/app/api/concerns/morpho/grape/user_password_reset.rb +13 -0
- data/app/api/concerns/morpho/grape/user_registration.rb +19 -0
- data/app/api/concerns/morpho/grape/user_unlock.rb +13 -0
- data/app/api/morpho/api.rb +24 -0
- data/app/api/morpho/entities/authentication_token.rb +8 -0
- data/app/api/morpho/entities/user.rb +7 -0
- data/app/api/morpho/entities/user_sign_in.rb +8 -0
- data/app/api/morpho/entities/user_sign_up.rb +9 -0
- data/app/api/morpho/resources/activations.rb +29 -0
- data/app/api/morpho/resources/passwords.rb +25 -0
- data/app/api/morpho/resources/tokens.rb +19 -0
- data/app/api/morpho/resources/unlocks.rb +29 -0
- data/app/api/morpho/resources/users.rb +19 -0
- data/app/assets/images/morpho/morpho.png +0 -0
- data/app/assets/images/morpho/morpho.svg +89 -0
- data/app/assets/stylesheets/morpho/application.css +74 -1
- data/app/controllers/morpho/activations_controller.rb +44 -0
- data/app/controllers/morpho/application_controller.rb +9 -0
- data/app/controllers/morpho/home_controller.rb +6 -0
- data/app/controllers/morpho/passwords_controller.rb +56 -0
- data/app/controllers/morpho/sessions_controller.rb +24 -0
- data/app/controllers/morpho/unlocks_controller.rb +44 -0
- data/app/controllers/morpho/users_controller.rb +25 -0
- data/app/mailers/morpho/application_mailer.rb +0 -1
- data/app/mailers/morpho/user_mailer.rb +31 -0
- data/app/models/morpho/authentication.rb +5 -0
- data/app/models/morpho/user.rb +60 -0
- data/app/views/layouts/morpho/application.html.erb +6 -4
- data/app/views/layouts/morpho/mailer.html.erb +13 -0
- data/app/views/layouts/morpho/mailer.text.erb +1 -0
- data/app/views/morpho/activations/new.html.erb +16 -0
- data/app/views/morpho/home/index.html.erb +4 -0
- data/app/views/morpho/passwords/edit.html.erb +18 -0
- data/app/views/morpho/passwords/new.html.erb +16 -0
- data/app/views/morpho/sessions/new.html.erb +23 -0
- data/app/views/morpho/unlocks/new.html.erb +16 -0
- data/app/views/morpho/user_mailer/activation_needed_email.html.erb +7 -0
- data/app/views/morpho/user_mailer/activation_needed_email.text.erb +7 -0
- data/app/views/morpho/user_mailer/activation_success_email.html.erb +7 -0
- data/app/views/morpho/user_mailer/activation_success_email.text.erb +7 -0
- data/app/views/morpho/user_mailer/reset_password_email.html.erb +7 -0
- data/app/views/morpho/user_mailer/reset_password_email.text.erb +7 -0
- data/app/views/morpho/user_mailer/unlock_token_email.html.erb +7 -0
- data/app/views/morpho/user_mailer/unlock_token_email.text.erb +7 -0
- data/app/views/morpho/users/new.html.erb +20 -0
- data/config/initializers/flash_rails_messages_skeleton.rb +22 -0
- data/config/initializers/simple_form.rb +182 -0
- data/config/initializers/sorcery.rb +513 -0
- data/config/locales/morpho.en.yml +93 -0
- data/config/routes.rb +25 -0
- data/db/migrate/20180919162009_sorcery_core.rb +13 -0
- data/db/migrate/20180919162055_sorcery_remember_me.rb +8 -0
- data/db/migrate/20180919162056_sorcery_reset_password.rb +10 -0
- data/db/migrate/20180919162057_sorcery_user_activation.rb +9 -0
- data/db/migrate/20180919162058_sorcery_brute_force_protection.rb +9 -0
- data/db/migrate/20180919162059_sorcery_activity_logging.rb +10 -0
- data/db/migrate/20180919162100_sorcery_external.rb +12 -0
- data/lib/generators/morpho/install/install_generator.rb +7 -0
- data/lib/generators/morpho/install/templates/config/initializers/morpho.rb +17 -0
- data/lib/generators/morpho/install/templates/public/favicon-16x16.png +0 -0
- data/lib/generators/morpho/install/templates/public/favicon-32x32.png +0 -0
- data/lib/generators/morpho/install/templates/public/favicon.ico +0 -0
- data/lib/morpho.rb +15 -2
- data/lib/morpho/configuration.rb +24 -0
- data/lib/morpho/configurations/api.rb +31 -0
- data/lib/morpho/configurations/auth.rb +11 -0
- data/lib/morpho/configurations/jwt.rb +17 -0
- data/lib/morpho/configurations/mailer.rb +23 -0
- data/lib/morpho/engine.rb +33 -0
- data/lib/morpho/loader.rb +11 -0
- data/lib/morpho/version.rb +1 -1
- data/lib/tasks/morpho_tasks.rake +1 -1
- data/lib/templates/erb/scaffold/_form.html.erb +15 -0
- metadata +223 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e508e3ad112945f755ed280843449493c214243896d9cb526b6d65fc6803c01c
|
|
4
|
+
data.tar.gz: 963b8391b3aa1e5955b1743dfa5b412618aa25ba24099264db011beaf3788a2b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df72bb86c829de2c8f18b419cbe38cbf60a47d39b3cb6c54a97000996708a8c7f20ba2d32ad3d632ca7e57a350cd830bf13e93b8958418a33c9cf7f502c34834
|
|
7
|
+
data.tar.gz: 50de12851535679fab3886e8cec10ee48f68a65b66fb057ecc3b445b880c6c982e48d908d91e96792285bbd6435f281cf4a9b2f548761fbbdace60ed083fe617
|
data/Rakefile
CHANGED
|
@@ -14,7 +14,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
|
14
14
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
APP_RAKEFILE = File.expand_path(
|
|
17
|
+
APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
|
|
18
18
|
load 'rails/tasks/engine.rake'
|
|
19
19
|
|
|
20
20
|
load 'rails/tasks/statistics.rake'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Grape
|
|
3
|
+
module HTTPResponses
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def render_bad_request
|
|
9
|
+
error!({ message: I18n.t('morpho.api.messages.bad_request') }, 400)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def render_unauthorized
|
|
13
|
+
error!({ message: I18n.t('morpho.api.messages.unauthorized') }, 401)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render_payment_required
|
|
17
|
+
error!({ message: I18n.t('morpho.api.messages.payment_required') }, 402)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render_forbidden
|
|
21
|
+
error!({ message: I18n.t('morpho.api.messages.forbidden') }, 403)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def render_not_found
|
|
25
|
+
error!({ message: I18n.t('morpho.api.messages.not_found') }, 404)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def render_method_not_allowed
|
|
29
|
+
error!({ message: I18n.t('morpho.api.messages.method_not_allowed') }, 405)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def render_unprocessable_entity(errors)
|
|
33
|
+
error!({ message: I18n.t('morpho.api.messages.unprocessable_entity'), errors: errors }, 422)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def render_no_content
|
|
37
|
+
body false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Grape
|
|
3
|
+
module JWTAuthentication
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def logged_in?
|
|
9
|
+
!!current_user
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def require_login
|
|
13
|
+
render_unauthorized unless logged_in?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def login(user_params)
|
|
17
|
+
user = User.find_by(email: user_params[:email])
|
|
18
|
+
|
|
19
|
+
if user
|
|
20
|
+
if user.active?
|
|
21
|
+
if !user.login_locked?
|
|
22
|
+
if user.valid_password?(user_params[:password])
|
|
23
|
+
token = user_payload(user)
|
|
24
|
+
|
|
25
|
+
present token, with: Morpho::Entities::AuthenticationToken
|
|
26
|
+
else
|
|
27
|
+
user.register_failed_login!
|
|
28
|
+
render_unauthorized
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
render_unauthorized
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
render_unauthorized
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
render_unauthorized
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def current_user
|
|
42
|
+
@current_user ||= Morpho::User.find_by(email: jwt_token[:email])
|
|
43
|
+
rescue
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def http_token
|
|
48
|
+
@http_token ||= if request.headers[Morpho.config.jwt.header].present?
|
|
49
|
+
request.headers[Morpho.config.jwt.header].split(' ').last
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def jwt_token
|
|
54
|
+
@jwt_token ||= HashWithIndifferentAccess.new(jwt_decode(http_token).first)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def jwt_encode(payload)
|
|
58
|
+
JWT.encode(payload, Morpho.config.jwt.secret, Morpho.config.jwt.algorithm)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def jwt_decode(token)
|
|
62
|
+
begin
|
|
63
|
+
return JWT.decode(token, Morpho.config.jwt.secret, true, { algorithm: Morpho.config.jwt.algorithm })
|
|
64
|
+
rescue
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def user_payload(user)
|
|
70
|
+
expires_at = Time.now.to_i + Morpho.config.jwt.expiration_time
|
|
71
|
+
issued_at = Time.now.to_i
|
|
72
|
+
|
|
73
|
+
{ token: jwt_encode({ exp: expires_at, iat: issued_at, email: user.email }), expires_at: expires_at }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Grape
|
|
3
|
+
module UserRegistration
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def register(user_params)
|
|
9
|
+
user = Morpho::User.new(user_params)
|
|
10
|
+
|
|
11
|
+
if user.save
|
|
12
|
+
present user, with: Morpho::Entities::User
|
|
13
|
+
else
|
|
14
|
+
render_unprocessable_entity(user.errors)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
class API < ::Grape::API
|
|
3
|
+
format :json
|
|
4
|
+
rescue_from :all
|
|
5
|
+
|
|
6
|
+
mount Morpho::Resources::Users
|
|
7
|
+
mount Morpho::Resources::Tokens
|
|
8
|
+
mount Morpho::Resources::Passwords
|
|
9
|
+
mount Morpho::Resources::Unlocks
|
|
10
|
+
mount Morpho::Resources::Activations
|
|
11
|
+
|
|
12
|
+
add_swagger_documentation({
|
|
13
|
+
info: {
|
|
14
|
+
title: Morpho.config.api.title,
|
|
15
|
+
description: Morpho.config.api.description,
|
|
16
|
+
models: Morpho.config.api.entities
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
route :any, '*path' do
|
|
21
|
+
error!({ message: I18n.t('morpho.api.messages.not_found') }, 404)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Entities
|
|
3
|
+
class AuthenticationToken < ::Grape::Entity
|
|
4
|
+
expose :token, documentation: { type: 'string', desc: 'User authentication token', required: true }
|
|
5
|
+
expose :expires_at, documentation: { type: 'string', desc: 'Authentication token expiration date in millis', required: true }
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Entities
|
|
3
|
+
class UserSignUp < ::Grape::Entity
|
|
4
|
+
expose :email, documentation: { type: 'string', desc: 'User email address' }
|
|
5
|
+
expose :password, documentation: { type: 'string', desc: 'User password' }
|
|
6
|
+
expose :password_confirmation, documentation: { type: 'string', desc: 'User password confirmation' }
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Resources
|
|
3
|
+
class Activations < ::Grape::API
|
|
4
|
+
helpers Morpho::Grape::HTTPResponses, Morpho::Grape::UserActivation
|
|
5
|
+
|
|
6
|
+
namespace :activations do
|
|
7
|
+
desc 'Request user activation token' do
|
|
8
|
+
success Morpho::Entities::User
|
|
9
|
+
end
|
|
10
|
+
params do
|
|
11
|
+
requires :user, type: Morpho::Entities::User
|
|
12
|
+
end
|
|
13
|
+
post do
|
|
14
|
+
if current_user
|
|
15
|
+
if !current_user.active?
|
|
16
|
+
current_user.resend_activation_needed_email!
|
|
17
|
+
|
|
18
|
+
present current_user, with: Morpho::Entities::User
|
|
19
|
+
else
|
|
20
|
+
render_method_not_allowed
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
render_not_found
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Resources
|
|
3
|
+
class Passwords < ::Grape::API
|
|
4
|
+
helpers Morpho::Grape::HTTPResponses, Morpho::Grape::UserPasswordReset
|
|
5
|
+
|
|
6
|
+
namespace :passwords do
|
|
7
|
+
desc 'Request user reset password token' do
|
|
8
|
+
success Morpho::Entities::User
|
|
9
|
+
end
|
|
10
|
+
params do
|
|
11
|
+
requires :user, type: Morpho::Entities::User
|
|
12
|
+
end
|
|
13
|
+
post do
|
|
14
|
+
if current_user
|
|
15
|
+
current_user.deliver_reset_password_instructions!
|
|
16
|
+
|
|
17
|
+
present current_user, with: Morpho::Entities::User
|
|
18
|
+
else
|
|
19
|
+
render_not_found
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Resources
|
|
3
|
+
class Tokens < ::Grape::API
|
|
4
|
+
helpers Morpho::Grape::HTTPResponses, Morpho::Grape::JWTAuthentication
|
|
5
|
+
|
|
6
|
+
namespace :tokens do
|
|
7
|
+
desc 'Request user authentication token' do
|
|
8
|
+
success Morpho::Entities::AuthenticationToken
|
|
9
|
+
end
|
|
10
|
+
params do
|
|
11
|
+
requires :user, type: Morpho::Entities::UserSignIn
|
|
12
|
+
end
|
|
13
|
+
post do
|
|
14
|
+
login(params[:user])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Resources
|
|
3
|
+
class Unlocks < ::Grape::API
|
|
4
|
+
helpers Morpho::Grape::HTTPResponses, Morpho::Grape::UserUnlock
|
|
5
|
+
|
|
6
|
+
namespace :unlocks do
|
|
7
|
+
desc 'Request user unlock token' do
|
|
8
|
+
success Morpho::Entities::User
|
|
9
|
+
end
|
|
10
|
+
params do
|
|
11
|
+
requires :user, type: Morpho::Entities::User
|
|
12
|
+
end
|
|
13
|
+
post do
|
|
14
|
+
if current_user
|
|
15
|
+
if current_user.login_locked?
|
|
16
|
+
current_user.resend_unlock_token_email!
|
|
17
|
+
|
|
18
|
+
present current_user, with: Morpho::Entities::User
|
|
19
|
+
else
|
|
20
|
+
render_method_not_allowed
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
render_not_found
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Morpho
|
|
2
|
+
module Resources
|
|
3
|
+
class Users < ::Grape::API
|
|
4
|
+
helpers Morpho::Grape::HTTPResponses, Morpho::Grape::UserRegistration
|
|
5
|
+
|
|
6
|
+
namespace :users do
|
|
7
|
+
desc 'User registration' do
|
|
8
|
+
success Morpho::Entities::User
|
|
9
|
+
end
|
|
10
|
+
params do
|
|
11
|
+
requires :user, type: Morpho::Entities::UserSignUp
|
|
12
|
+
end
|
|
13
|
+
post do
|
|
14
|
+
register(params[:user])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
2
|
+
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
+
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
5
|
+
width="35.616px" height="35.616px" viewBox="0 0 35.616 35.616" style="enable-background:new 0 0 35.616 35.616;"
|
|
6
|
+
xml:space="preserve">
|
|
7
|
+
<g>
|
|
8
|
+
<g>
|
|
9
|
+
<polygon points="7.526,6.141 7.526,11.054 11.78,8.598 "/>
|
|
10
|
+
<polygon points="7.526,12.282 7.526,17.193 11.78,14.738 "/>
|
|
11
|
+
<polygon points="7.526,24.562 7.526,29.475 11.78,27.02 "/>
|
|
12
|
+
<polygon points="7.881,30.09 12.135,32.546 12.135,27.635 "/>
|
|
13
|
+
<polygon points="7.881,17.809 12.135,20.266 12.135,15.352 "/>
|
|
14
|
+
<polygon points="7.881,11.668 12.135,14.124 12.135,9.211 "/>
|
|
15
|
+
<polygon points="7.881,5.527 12.135,7.983 12.135,3.071 "/>
|
|
16
|
+
<polygon points="12.844,3.071 12.844,7.983 17.099,5.527 "/>
|
|
17
|
+
<polygon points="12.844,9.211 12.844,14.124 17.099,11.668 "/>
|
|
18
|
+
<polygon points="12.844,15.352 12.844,20.266 17.099,17.809 "/>
|
|
19
|
+
<polygon points="12.844,21.493 12.844,26.405 17.099,23.949 "/>
|
|
20
|
+
<polygon points="12.844,27.635 12.844,32.546 17.099,30.09 "/>
|
|
21
|
+
<polygon points="13.199,33.16 17.453,35.616 17.453,30.704 "/>
|
|
22
|
+
<polygon points="13.199,27.02 17.453,29.475 17.453,24.562 "/>
|
|
23
|
+
<polygon points="13.199,20.879 17.453,23.335 17.453,18.422 "/>
|
|
24
|
+
<polygon points="13.199,14.738 17.453,17.193 17.453,12.282 "/>
|
|
25
|
+
<polygon points="13.199,2.456 17.453,4.913 17.453,0 "/>
|
|
26
|
+
<polygon points="2.208,9.211 2.208,14.124 6.462,11.668 "/>
|
|
27
|
+
<polygon points="2.208,15.352 2.208,20.266 6.462,17.809 "/>
|
|
28
|
+
<polygon points="2.208,21.493 2.208,26.405 6.462,23.949 "/>
|
|
29
|
+
<polygon points="2.562,27.02 6.817,29.475 6.817,24.562 "/>
|
|
30
|
+
<polygon points="2.562,20.879 6.817,23.335 6.817,18.422 "/>
|
|
31
|
+
<polygon points="2.562,14.738 6.817,17.193 6.817,12.282 "/>
|
|
32
|
+
<polygon points="2.562,8.598 6.817,11.054 6.817,6.141 "/>
|
|
33
|
+
<polygon points="18.162,0 18.162,4.913 22.416,2.456 "/>
|
|
34
|
+
<polygon points="18.162,12.282 18.162,17.193 22.416,14.738 "/>
|
|
35
|
+
<polygon points="18.162,18.422 18.162,23.335 22.416,20.879 "/>
|
|
36
|
+
<polygon points="18.162,24.562 18.162,29.475 22.416,27.02 "/>
|
|
37
|
+
<polygon points="18.162,30.704 18.162,35.616 22.416,33.16 "/>
|
|
38
|
+
<polygon points="18.517,30.09 22.771,32.546 22.771,27.635 "/>
|
|
39
|
+
<polygon points="18.517,23.949 22.771,26.405 22.771,21.493 "/>
|
|
40
|
+
<polygon points="18.517,17.809 22.771,20.266 22.771,15.352 "/>
|
|
41
|
+
<polygon points="18.517,11.668 22.771,14.124 22.771,9.211 "/>
|
|
42
|
+
<polygon points="18.517,5.527 22.771,7.983 22.771,3.071 "/>
|
|
43
|
+
<polygon points="23.479,3.071 23.479,7.983 27.735,5.527 "/>
|
|
44
|
+
<polygon points="23.479,9.211 23.479,14.124 27.735,11.668 "/>
|
|
45
|
+
<polygon points="23.479,15.352 23.479,20.266 27.735,17.809 "/>
|
|
46
|
+
<polygon points="23.479,27.635 23.479,32.546 27.735,30.09 "/>
|
|
47
|
+
<polygon points="23.835,27.02 28.089,29.475 28.089,24.562 "/>
|
|
48
|
+
<polygon points="23.835,14.738 28.089,17.193 28.089,12.282 "/>
|
|
49
|
+
<polygon points="23.835,8.598 28.089,11.054 28.089,6.141 "/>
|
|
50
|
+
<polygon points="28.798,6.141 28.798,11.054 33.052,8.598 "/>
|
|
51
|
+
<polygon points="28.798,12.282 28.798,17.193 33.052,14.738 "/>
|
|
52
|
+
<polygon points="28.798,18.422 28.798,23.335 33.052,20.879 "/>
|
|
53
|
+
<polygon points="28.798,24.562 28.798,29.475 33.052,27.02 "/>
|
|
54
|
+
<polygon points="29.153,23.949 33.408,26.405 33.408,21.493 "/>
|
|
55
|
+
<polygon points="29.153,17.809 33.408,20.266 33.408,15.352 "/>
|
|
56
|
+
<polygon points="29.153,11.668 33.408,14.124 33.408,9.211 "/>
|
|
57
|
+
</g>
|
|
58
|
+
</g>
|
|
59
|
+
<g>
|
|
60
|
+
</g>
|
|
61
|
+
<g>
|
|
62
|
+
</g>
|
|
63
|
+
<g>
|
|
64
|
+
</g>
|
|
65
|
+
<g>
|
|
66
|
+
</g>
|
|
67
|
+
<g>
|
|
68
|
+
</g>
|
|
69
|
+
<g>
|
|
70
|
+
</g>
|
|
71
|
+
<g>
|
|
72
|
+
</g>
|
|
73
|
+
<g>
|
|
74
|
+
</g>
|
|
75
|
+
<g>
|
|
76
|
+
</g>
|
|
77
|
+
<g>
|
|
78
|
+
</g>
|
|
79
|
+
<g>
|
|
80
|
+
</g>
|
|
81
|
+
<g>
|
|
82
|
+
</g>
|
|
83
|
+
<g>
|
|
84
|
+
</g>
|
|
85
|
+
<g>
|
|
86
|
+
</g>
|
|
87
|
+
<g>
|
|
88
|
+
</g>
|
|
89
|
+
</svg>
|