ditty 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.pryrc +6 -0
- data/.rspec +2 -0
- data/.rubocop.yml +15 -0
- data/.travis.yml +15 -0
- data/Gemfile.ci +19 -0
- data/License.txt +7 -0
- data/Rakefile +10 -0
- data/Readme.md +67 -0
- data/config.ru +33 -0
- data/ditty.gemspec +46 -0
- data/lib/ditty/components/app.rb +78 -0
- data/lib/ditty/controllers/application.rb +79 -0
- data/lib/ditty/controllers/audit_logs.rb +44 -0
- data/lib/ditty/controllers/component.rb +161 -0
- data/lib/ditty/controllers/main.rb +86 -0
- data/lib/ditty/controllers/roles.rb +16 -0
- data/lib/ditty/controllers/users.rb +183 -0
- data/lib/ditty/db.rb +12 -0
- data/lib/ditty/helpers/authentication.rb +58 -0
- data/lib/ditty/helpers/component.rb +63 -0
- data/lib/ditty/helpers/pundit.rb +34 -0
- data/lib/ditty/helpers/views.rb +50 -0
- data/lib/ditty/helpers/wisper.rb +14 -0
- data/lib/ditty/listener.rb +23 -0
- data/lib/ditty/models/audit_log.rb +14 -0
- data/lib/ditty/models/base.rb +7 -0
- data/lib/ditty/models/identity.rb +70 -0
- data/lib/ditty/models/role.rb +16 -0
- data/lib/ditty/models/user.rb +63 -0
- data/lib/ditty/policies/application_policy.rb +21 -0
- data/lib/ditty/policies/audit_log_policy.rb +41 -0
- data/lib/ditty/policies/identity_policy.rb +25 -0
- data/lib/ditty/policies/role_policy.rb +41 -0
- data/lib/ditty/policies/user_policy.rb +47 -0
- data/lib/ditty/rake_tasks.rb +85 -0
- data/lib/ditty/seed.rb +1 -0
- data/lib/ditty/services/logger.rb +48 -0
- data/lib/ditty/version.rb +5 -0
- data/lib/ditty.rb +142 -0
- data/migrate/20170207_base_tables.rb +40 -0
- data/migrate/20170208_audit_log.rb +12 -0
- data/migrate/20170416_audit_log_details.rb +9 -0
- data/public/browserconfig.xml +9 -0
- data/public/images/apple-icon.png +0 -0
- data/public/images/favicon-16x16.png +0 -0
- data/public/images/favicon-32x32.png +0 -0
- data/public/images/launcher-icon-1x.png +0 -0
- data/public/images/launcher-icon-2x.png +0 -0
- data/public/images/launcher-icon-4x.png +0 -0
- data/public/images/mstile-150x150.png +0 -0
- data/public/images/safari-pinned-tab.svg +43 -0
- data/public/manifest.json +25 -0
- data/views/404.haml +7 -0
- data/views/audit_logs/index.haml +30 -0
- data/views/error.haml +4 -0
- data/views/identity/login.haml +19 -0
- data/views/identity/register.haml +14 -0
- data/views/index.haml +1 -0
- data/views/layout.haml +55 -0
- data/views/partials/delete_form.haml +4 -0
- data/views/partials/footer.haml +5 -0
- data/views/partials/form_control.haml +20 -0
- data/views/partials/navbar.haml +24 -0
- data/views/partials/notifications.haml +24 -0
- data/views/partials/pager.haml +14 -0
- data/views/partials/sidebar.haml +35 -0
- data/views/roles/display.haml +18 -0
- data/views/roles/edit.haml +11 -0
- data/views/roles/form.haml +1 -0
- data/views/roles/index.haml +22 -0
- data/views/roles/new.haml +10 -0
- data/views/users/display.haml +50 -0
- data/views/users/edit.haml +11 -0
- data/views/users/identity.haml +3 -0
- data/views/users/index.haml +23 -0
- data/views/users/new.haml +11 -0
- data/views/users/profile.haml +39 -0
- data/views/users/user.haml +3 -0
- metadata +431 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/models/base'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/core_ext/object/blank'
|
7
|
+
|
8
|
+
# Why not store this in Elasticsearch?
|
9
|
+
module Ditty
|
10
|
+
class User < Sequel::Model
|
11
|
+
include ::Ditty::Base
|
12
|
+
|
13
|
+
one_to_many :identity
|
14
|
+
many_to_many :roles
|
15
|
+
one_to_many :audit_logs
|
16
|
+
|
17
|
+
def role?(check)
|
18
|
+
!roles_dataset.first(name: check).nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(method_sym, *arguments, &block)
|
22
|
+
if method_sym.to_s[-1] == '?'
|
23
|
+
role?(method_sym[0..-2])
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def respond_to_missing?(name, _include_private = false)
|
30
|
+
name[-1] == '?'
|
31
|
+
end
|
32
|
+
|
33
|
+
def gravatar
|
34
|
+
hash = Digest::MD5.hexdigest(email.downcase)
|
35
|
+
"https://www.gravatar.com/avatar/#{hash}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate
|
39
|
+
validates_presence :email
|
40
|
+
return if email.blank?
|
41
|
+
validates_unique :email
|
42
|
+
validates_format(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :email)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Add the basic roles and identity
|
46
|
+
def after_create
|
47
|
+
check_roles
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_roles
|
51
|
+
return if role?('anonymous')
|
52
|
+
add_role Role.find_or_create(name: 'user') unless role?('user')
|
53
|
+
end
|
54
|
+
|
55
|
+
def index_prefix
|
56
|
+
email
|
57
|
+
end
|
58
|
+
|
59
|
+
def username
|
60
|
+
identity_dataset.first.username
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ditty
|
4
|
+
class ApplicationPolicy
|
5
|
+
attr_reader :user, :record
|
6
|
+
|
7
|
+
def initialize(user, record)
|
8
|
+
@user = user
|
9
|
+
@record = record
|
10
|
+
end
|
11
|
+
|
12
|
+
class Scope
|
13
|
+
attr_reader :user, :scope
|
14
|
+
|
15
|
+
def initialize(user, scope)
|
16
|
+
@user = user
|
17
|
+
@scope = scope
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/policies/application_policy'
|
4
|
+
|
5
|
+
module Ditty
|
6
|
+
class AuditLogPolicy < ApplicationPolicy
|
7
|
+
def create?
|
8
|
+
user && user.super_admin?
|
9
|
+
end
|
10
|
+
|
11
|
+
def list?
|
12
|
+
create?
|
13
|
+
end
|
14
|
+
|
15
|
+
def read?
|
16
|
+
create?
|
17
|
+
end
|
18
|
+
|
19
|
+
def update?
|
20
|
+
read?
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete?
|
24
|
+
create?
|
25
|
+
end
|
26
|
+
|
27
|
+
def permitted_attributes
|
28
|
+
%i[action details]
|
29
|
+
end
|
30
|
+
|
31
|
+
class Scope < ApplicationPolicy::Scope
|
32
|
+
def resolve
|
33
|
+
if user && user.super_admin?
|
34
|
+
scope
|
35
|
+
else
|
36
|
+
scope.where(id: -1)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/policies/application_policy'
|
4
|
+
|
5
|
+
module Ditty
|
6
|
+
class IdentityPolicy < ApplicationPolicy
|
7
|
+
def register?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def permitted_attributes
|
12
|
+
%i[username password password_confirmation]
|
13
|
+
end
|
14
|
+
|
15
|
+
class Scope < ApplicationPolicy::Scope
|
16
|
+
def resolve
|
17
|
+
if user.super_admin?
|
18
|
+
scope.all
|
19
|
+
else
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/policies/application_policy'
|
4
|
+
|
5
|
+
module Ditty
|
6
|
+
class RolePolicy < ApplicationPolicy
|
7
|
+
def create?
|
8
|
+
user && user.super_admin?
|
9
|
+
end
|
10
|
+
|
11
|
+
def list?
|
12
|
+
create?
|
13
|
+
end
|
14
|
+
|
15
|
+
def read?
|
16
|
+
create?
|
17
|
+
end
|
18
|
+
|
19
|
+
def update?
|
20
|
+
read?
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete?
|
24
|
+
create?
|
25
|
+
end
|
26
|
+
|
27
|
+
def permitted_attributes
|
28
|
+
[:name]
|
29
|
+
end
|
30
|
+
|
31
|
+
class Scope < ApplicationPolicy::Scope
|
32
|
+
def resolve
|
33
|
+
if user && user.super_admin?
|
34
|
+
scope
|
35
|
+
else
|
36
|
+
scope.where(id: -1)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/policies/application_policy'
|
4
|
+
|
5
|
+
module Ditty
|
6
|
+
class UserPolicy < ApplicationPolicy
|
7
|
+
def create?
|
8
|
+
user && user.super_admin?
|
9
|
+
end
|
10
|
+
|
11
|
+
def list?
|
12
|
+
create?
|
13
|
+
end
|
14
|
+
|
15
|
+
def read?
|
16
|
+
user && (record.id == user.id || user.super_admin?)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update?
|
20
|
+
read?
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete?
|
24
|
+
create?
|
25
|
+
end
|
26
|
+
|
27
|
+
def register?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def permitted_attributes
|
32
|
+
attribs = %i[email name surname]
|
33
|
+
attribs << :role_id if user.super_admin?
|
34
|
+
attribs
|
35
|
+
end
|
36
|
+
|
37
|
+
class Scope < ApplicationPolicy::Scope
|
38
|
+
def resolve
|
39
|
+
if user && user.super_admin?
|
40
|
+
scope
|
41
|
+
else
|
42
|
+
scope.where(id: user.id)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/tasklib'
|
5
|
+
|
6
|
+
module Ditty
|
7
|
+
class Tasks < ::Rake::TaskLib
|
8
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
9
|
+
|
10
|
+
def install_tasks
|
11
|
+
namespace :ditty do
|
12
|
+
desc 'Generate the needed tokens'
|
13
|
+
task :generate_tokens do
|
14
|
+
puts 'Generating the Ditty tokens'
|
15
|
+
require 'securerandom'
|
16
|
+
File.write('.session_secret', SecureRandom.random_bytes(40)) unless File.file?('.session_secret')
|
17
|
+
File.write('.token_secret', SecureRandom.random_bytes(40)) unless File.file?('.token_secret')
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'Seed the Ditty database'
|
21
|
+
task :seed do
|
22
|
+
puts 'Seeding the Ditty database'
|
23
|
+
require 'ditty/seed'
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'Prepare Ditty migrations'
|
27
|
+
task :prep do
|
28
|
+
puts 'Prepare the Ditty folders'
|
29
|
+
Dir.mkdir 'pids' unless File.exist?('pids')
|
30
|
+
|
31
|
+
puts 'Preparing the Ditty migrations folder'
|
32
|
+
Dir.mkdir 'migrations' unless File.exist?('migrations')
|
33
|
+
::Ditty::Components.migrations.each do |path|
|
34
|
+
FileUtils.cp_r "#{path}/.", 'migrations'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'Migrate Ditty database to latest version'
|
39
|
+
task :migrate do
|
40
|
+
puts 'Running the Ditty migrations'
|
41
|
+
Rake::Task['ditty:migrate:up'].invoke
|
42
|
+
end
|
43
|
+
|
44
|
+
namespace :migrate do
|
45
|
+
folder = 'migrations'
|
46
|
+
|
47
|
+
desc 'Check if the migration is current'
|
48
|
+
task :check do
|
49
|
+
require 'sequel'
|
50
|
+
puts 'Running Ditty Migrations check'
|
51
|
+
::Sequel.extension :migration
|
52
|
+
::Sequel::Migrator.check_current(::DB, folder)
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'Migrate Ditty database to latest version'
|
56
|
+
task :up do
|
57
|
+
require 'sequel'
|
58
|
+
puts 'Running Ditty Migrations up'
|
59
|
+
::Sequel.extension :migration
|
60
|
+
::Sequel::Migrator.apply(::DB, folder)
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'Roll back the Ditty database'
|
64
|
+
task :down do
|
65
|
+
require 'sequel'
|
66
|
+
puts 'Running Ditty Migrations down'
|
67
|
+
::Sequel.extension :migration
|
68
|
+
::Sequel::Migrator.apply(::DB, folder, 0)
|
69
|
+
end
|
70
|
+
|
71
|
+
desc 'Reset the Ditty database'
|
72
|
+
task :bounce do
|
73
|
+
require 'sequel'
|
74
|
+
puts 'Running Ditty Migrations bounce'
|
75
|
+
::Sequel.extension :migration
|
76
|
+
::Sequel::Migrator.apply(::DB, folder, 0)
|
77
|
+
::Sequel::Migrator.apply(::DB, folder)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Ditty::Tasks.new.install_tasks
|
data/lib/ditty/seed.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
::Ditty::Components.seeders.each(&:call)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'yaml'
|
5
|
+
require 'singleton'
|
6
|
+
require 'active_support/inflector'
|
7
|
+
|
8
|
+
module Ditty
|
9
|
+
module Services
|
10
|
+
class Logger
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
CONFIG = './config/logger.yml'.freeze
|
14
|
+
attr_reader :loggers
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@loggers = []
|
18
|
+
config.each do |values|
|
19
|
+
klass = values['class'].constantize
|
20
|
+
opts = values['options'] || nil
|
21
|
+
logger = klass.new(opts)
|
22
|
+
if values['level']
|
23
|
+
logger.level = klass.const_get(values['level'].to_sym)
|
24
|
+
end
|
25
|
+
@loggers << logger
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(method, *args, &block)
|
30
|
+
loggers.each { |logger| logger.send(method, *args, &block) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def respond_to_missing?(method, _include_private = false)
|
34
|
+
loggers.any? { |logger| logger.respond_to?(method) }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def config
|
40
|
+
@config ||= File.exist?(CONFIG) ? YAML.load_file(CONFIG) : default
|
41
|
+
end
|
42
|
+
|
43
|
+
def default
|
44
|
+
[{ 'name' => 'default', 'class' => 'Logger' }]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/ditty.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/version'
|
4
|
+
|
5
|
+
module Ditty
|
6
|
+
class ComponentError < StandardError; end
|
7
|
+
|
8
|
+
# A thread safe cache class, offering only #[] and #[]= methods,
|
9
|
+
# each protected by a mutex.
|
10
|
+
# Ripped off from Roda - https://github.com/jeremyevans/roda
|
11
|
+
class ComponentCache
|
12
|
+
# Create a new thread safe cache.
|
13
|
+
def initialize
|
14
|
+
@mutex = Mutex.new
|
15
|
+
@hash = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Make getting value from underlying hash thread safe.
|
19
|
+
def [](key)
|
20
|
+
@mutex.synchronize { @hash[key] }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Make setting value in underlying hash thread safe.
|
24
|
+
def []=(key, value)
|
25
|
+
@mutex.synchronize { @hash[key] = value }
|
26
|
+
end
|
27
|
+
|
28
|
+
def map(&block)
|
29
|
+
@mutex.synchronize { @hash.map(&block) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def inject(memo, &block)
|
33
|
+
@mutex.synchronize { @hash.inject(memo, &block) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Ripped off from Roda - https://github.com/jeremyevans/roda
|
38
|
+
module Components
|
39
|
+
# Stores registered components
|
40
|
+
@components = ComponentCache.new
|
41
|
+
|
42
|
+
# If the registered component already exists, use it. Otherwise,
|
43
|
+
# require it and return it. This raises a LoadError if such a
|
44
|
+
# component doesn't exist, or a Component if it exists but it does
|
45
|
+
# not register itself correctly.
|
46
|
+
def self.load_component(name)
|
47
|
+
h = @components
|
48
|
+
unless (component = h[name])
|
49
|
+
require "ditty/components/#{name}"
|
50
|
+
raise ComponentError, "Component #{name} did not register itself correctly in Ditty::Components" unless (component = h[name])
|
51
|
+
end
|
52
|
+
component
|
53
|
+
end
|
54
|
+
|
55
|
+
# Register the given component with Component, so that it can be loaded using #component
|
56
|
+
# with a symbol. Should be used by component files. Example:
|
57
|
+
#
|
58
|
+
# Ditty::Components.register_component(:component_name, ComponentModule)
|
59
|
+
def self.register_component(name, mod)
|
60
|
+
puts "Registering #{mod} as #{name}"
|
61
|
+
@components[name] = mod
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.components
|
65
|
+
@components
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return a hash of controllers with their routes as keys: `{ '/users' => Ditty::Controllers::Users }`
|
69
|
+
def self.routes
|
70
|
+
@routes ||= {}
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.routes=(routes)
|
74
|
+
@routes = routes
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return an ordered list of navigation items:
|
78
|
+
# `[{order:0, link:'/users/', text:'Users'}, {order:1, link:'/roles/', text:'Roles'}]
|
79
|
+
def self.navigation
|
80
|
+
@navigation ||= []
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.navigation=(navigation)
|
84
|
+
@navigation = navigation
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.migrations
|
88
|
+
@migrations ||= []
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.migrations=(migrations)
|
92
|
+
@migrations = migrations
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.seeders
|
96
|
+
@seeders ||= []
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.seeders=(seeders)
|
100
|
+
@seeders = seeders
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.workers
|
104
|
+
@workers ||= []
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.workers=(workers)
|
108
|
+
@workers = workers
|
109
|
+
end
|
110
|
+
|
111
|
+
module Base
|
112
|
+
module ClassMethods
|
113
|
+
# Load a new component into the current class. A component can be a module
|
114
|
+
# which is used directly, or a symbol represented a registered component
|
115
|
+
# which will be required and then used. Returns nil.
|
116
|
+
#
|
117
|
+
# Component.component ComponentModule
|
118
|
+
# Component.component :csrf
|
119
|
+
def component(component, *args, &block)
|
120
|
+
raise ComponentError, 'Cannot add a component to a frozen Component class' if frozen?
|
121
|
+
component = Components.load_component(component) if component.is_a?(Symbol)
|
122
|
+
include(component::InstanceMethods) if defined?(component::InstanceMethods)
|
123
|
+
extend(component::ClassMethods) if defined?(component::ClassMethods)
|
124
|
+
|
125
|
+
component.configure(self, *args, &block) if component.respond_to?(:configure)
|
126
|
+
Components.navigation.concat component.navigation if component.respond_to?(:navigation)
|
127
|
+
Components.routes.merge! component.routes if component.respond_to?(:routes)
|
128
|
+
Components.migrations << component.migrations if component.respond_to?(:migrations)
|
129
|
+
Components.seeders << component.seeder if component.respond_to?(:seeder)
|
130
|
+
Components.workers.concat component.workers if component.respond_to?(:workers)
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
module InstanceMethods
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
extend Components::Base::ClassMethods
|
141
|
+
component Components::Base
|
142
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Sequel.migration do
|
4
|
+
change do
|
5
|
+
create_table :users do
|
6
|
+
primary_key :id
|
7
|
+
String :name
|
8
|
+
String :surname
|
9
|
+
String :email
|
10
|
+
DateTime :created_at
|
11
|
+
DateTime :updated_at
|
12
|
+
unique [:email]
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :identities do
|
16
|
+
primary_key :id
|
17
|
+
foreign_key :user_id, :users
|
18
|
+
String :username
|
19
|
+
String :crypted_password
|
20
|
+
DateTime :created_at
|
21
|
+
DateTime :updated_at
|
22
|
+
unique [:username]
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table :roles do
|
26
|
+
primary_key :id
|
27
|
+
String :name
|
28
|
+
DateTime :created_at
|
29
|
+
DateTime :updated_at
|
30
|
+
unique [:name]
|
31
|
+
end
|
32
|
+
|
33
|
+
create_table :roles_users do
|
34
|
+
DateTime :created_at
|
35
|
+
foreign_key :user_id, :users
|
36
|
+
foreign_key :role_id, :roles
|
37
|
+
unique %i[user_id role_id]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
3
|
+
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
4
|
+
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
5
|
+
width="192.000000pt" height="192.000000pt" viewBox="0 0 192.000000 192.000000"
|
6
|
+
preserveAspectRatio="xMidYMid meet">
|
7
|
+
<metadata>
|
8
|
+
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
9
|
+
</metadata>
|
10
|
+
<g transform="translate(0.000000,192.000000) scale(0.100000,-0.100000)"
|
11
|
+
fill="#000000" stroke="none">
|
12
|
+
<path d="M812 1896 c-3 -6 -7 -46 -7 -88 -1 -43 -4 -80 -8 -84 -3 -4 -26 -9
|
13
|
+
-49 -11 -97 -11 -247 -74 -343 -146 -324 -242 -395 -717 -159 -1050 19 -26 34
|
14
|
+
-49 34 -52 0 -2 -27 -32 -60 -65 -33 -33 -60 -63 -60 -67 0 -12 121 -119 175
|
15
|
+
-154 116 -76 273 -139 380 -153 61 -8 251 -9 285 -1 14 3 42 8 62 10 95 13
|
16
|
+
280 99 381 178 182 142 294 322 346 554 31 136 27 266 -13 449 -9 41 -59 159
|
17
|
+
-94 219 -83 144 -209 268 -355 349 -71 40 -206 91 -263 101 -130 21 -242 26
|
18
|
+
-252 11z m240 -135 c151 -34 301 -120 411 -236 83 -87 184 -269 201 -360 3
|
19
|
+
-16 9 -55 15 -85 11 -64 7 -213 -7 -285 -61 -297 -283 -537 -577 -624 -86 -25
|
20
|
+
-122 -30 -216 -31 -188 -3 -347 48 -507 163 l-43 31 300 300 299 301 0 135 c0
|
21
|
+
74 1 265 1 423 l1 289 38 -5 c20 -3 58 -11 84 -16z m-248 -256 l1 -88 -50 -14
|
22
|
+
c-295 -82 -433 -407 -285 -670 l30 -52 -60 -61 c-32 -33 -63 -60 -67 -60 -25
|
23
|
+
1 -97 139 -124 236 -40 146 -19 336 52 463 91 163 220 265 404 319 99 30 99
|
24
|
+
30 99 -73z m1 -361 c1 -76 -3 -147 -8 -158 -5 -10 -53 -63 -108 -117 l-99 -99
|
25
|
+
-15 24 c-70 106 -51 283 42 386 45 50 153 115 178 107 6 -2 10 -59 10 -143z"/>
|
26
|
+
<path d="M990 1600 l0 -30 168 0 167 0 -45 30 c-44 29 -48 30 -167 30 l-123 0
|
27
|
+
0 -30z"/>
|
28
|
+
<path d="M990 1417 l0 -32 250 0 c138 0 250 3 250 6 0 4 -9 17 -19 31 l-20 25
|
29
|
+
-230 1 -231 1 0 -32z"/>
|
30
|
+
<path d="M994 1261 c-2 -2 -4 -16 -4 -31 l0 -27 299 0 300 0 -13 31 -12 31
|
31
|
+
-283 0 c-155 0 -284 -2 -287 -4z"/>
|
32
|
+
<path d="M994 1081 c-2 -2 -4 -17 -4 -33 l0 -28 315 0 315 0 0 30 c0 24 -4 30
|
33
|
+
-22 31 -116 3 -601 3 -604 0z"/>
|
34
|
+
<path d="M959 870 l-32 -30 39 -1 c132 -2 646 -1 649 2 1 2 5 15 7 29 l3 25
|
35
|
+
-317 3 -318 2 -31 -30z"/>
|
36
|
+
<path d="M777 690 c-15 -15 -27 -29 -27 -31 0 -2 183 -4 407 -4 l407 0 12 31
|
37
|
+
13 31 -393 0 -393 0 -26 -27z"/>
|
38
|
+
<path d="M600 510 c-17 -14 -30 -28 -30 -32 0 -3 196 -6 436 -6 l437 0 23 24
|
39
|
+
c13 12 24 27 24 31 0 4 -194 8 -430 8 l-431 0 -29 -25z"/>
|
40
|
+
<path d="M430 341 c0 -5 16 -18 36 -30 34 -20 47 -21 402 -21 l367 0 45 30 45
|
41
|
+
30 -447 0 c-260 0 -448 -4 -448 -9z"/>
|
42
|
+
</g>
|
43
|
+
</svg>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
{
|
2
|
+
"short_name": "Ditty",
|
3
|
+
"name": "Ditty",
|
4
|
+
"icons": [
|
5
|
+
{
|
6
|
+
"src": "images/launcher-icon-1x.png",
|
7
|
+
"type": "image/png",
|
8
|
+
"sizes": "48x48"
|
9
|
+
},
|
10
|
+
{
|
11
|
+
"src": "images/launcher-icon-2x.png",
|
12
|
+
"type": "image/png",
|
13
|
+
"sizes": "96x96"
|
14
|
+
},
|
15
|
+
{
|
16
|
+
"src": "images/launcher-icon-4x.png",
|
17
|
+
"type": "image/png",
|
18
|
+
"sizes": "192x192"
|
19
|
+
}
|
20
|
+
],
|
21
|
+
"start_url": "auth/identity",
|
22
|
+
"theme_color": "#ffffff",
|
23
|
+
"background_color": "#ffffff",
|
24
|
+
"display": "standalone"
|
25
|
+
}
|