ditty 0.2.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 +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
|
+
}
|