sinja 0.2.0.beta2 → 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
+ require 'rake/testtask'
3
4
 
4
- RSpec::Core::RakeTask.new(:spec)
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList[File.expand_path('test/**/*_test.rb', __dir__)]
7
+ t.warning = false
8
+ end
5
9
 
6
- task :default=>:spec
10
+ task default: :test
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path=>'..'
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ require 'sinatra'
3
+ require 'sinatra/jsonapi'
4
+
5
+ require_relative 'classes/author'
6
+ require_relative 'classes/comment'
7
+ require_relative 'classes/post'
8
+ require_relative 'classes/tag'
9
+
10
+ require 'sinja/helpers/sequel'
11
+
12
+ configure_jsonapi do |c|
13
+ Sinja::Helpers::Sequel.config(c)
14
+ end
15
+
16
+ helpers Sinja::Helpers::Sequel do
17
+ def current_user
18
+ # TESTING/DEMO PURPOSES ONLY -- DO NOT DO THIS IN PRODUCTION
19
+ Author.first_by_email(env['HTTP_X_EMAIL']) if env.key?('HTTP_X_EMAIL')
20
+ end
21
+
22
+ def role
23
+ [].tap do |a|
24
+ a << :logged_in if current_user
25
+ a << :superuser if current_user&.admin?
26
+ end
27
+ end
28
+
29
+ def database
30
+ DB
31
+ end
32
+ end
33
+
34
+ resource :authors, AuthorController
35
+ resource :comments, CommentController
36
+ resource :posts, PostController
37
+ resource :tags, TagController
38
+
39
+ freeze_jsonapi
@@ -0,0 +1,9 @@
1
+ # require_string_literal: true
2
+ require_relative 'boot'
3
+
4
+ class BaseSerializer
5
+ include JSONAPI::Serializer
6
+ end
7
+
8
+ Sequel::Model.plugin :tactical_eager_loading
9
+ Sequel::Model.plugin :validation_helpers
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ require 'bundler/setup'
3
+
4
+ Bundler.require(:default, :development)
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../base'
3
+ require_relative '../database'
4
+
5
+ DB.create_table?(:authors) do
6
+ primary_key :id
7
+ String :email, :null=>false, :unique=>true
8
+ String :real_name
9
+ String :display_name
10
+ TrueClass :admin, :default=>false
11
+ DateTime :created_at
12
+ DateTime :updated_at
13
+ end
14
+
15
+ class Author < Sequel::Model
16
+ plugin :timestamps
17
+ plugin :boolean_readers
18
+
19
+ finder def self.by_email(arg)
20
+ where(:email=>arg)
21
+ end
22
+
23
+ one_to_many :comments
24
+ one_to_many :posts
25
+ end
26
+
27
+ Author.create(email: 'all@yourbase.com', admin: true)
28
+
29
+ class AuthorSerializer < BaseSerializer
30
+ attribute(:display_name) { object.display_name || 'Anonymous Coward' }
31
+
32
+ has_many :comments
33
+ has_many :posts
34
+ end
35
+
36
+ AuthorController = proc do
37
+ helpers do
38
+ def find(id)
39
+ Author[id.to_i]
40
+ end
41
+
42
+ def role
43
+ if resource == current_user
44
+ super.push(:self)
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def fields
51
+ %i[email real_name display_name].tap do |a|
52
+ a << :admin if role?(:superuser)
53
+ end
54
+ end
55
+ end
56
+
57
+ show
58
+
59
+ index do
60
+ Author.all
61
+ end
62
+
63
+ create do |attr|
64
+ author = Author.new
65
+ author.set_fields(attr, fields)
66
+ next_pk author.save(validate: false)
67
+ end
68
+
69
+ update(roles: %i[self superuser]) do |attr|
70
+ resource.update_fields(attr, fields, validate: false, missing: :skip)
71
+ end
72
+
73
+ destroy(roles: %i[self superuser]) do
74
+ resource.destroy
75
+ end
76
+
77
+ has_many :comments do
78
+ fetch(roles: :logged_in) do
79
+ resource.comments
80
+ end
81
+ end
82
+
83
+ has_many :posts do
84
+ fetch do
85
+ resource.posts
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../base'
3
+ require_relative '../database'
4
+
5
+ DB.create_table?(:comments) do
6
+ primary_key :id
7
+ foreign_key :author_id, :authors, :on_delete=>:cascade
8
+ foreign_key :post_slug, :posts, :on_delete=>:cascade, :type=>String
9
+ String :body, :text=>true, :null=>false
10
+ DateTime :created_at
11
+ DateTime :updated_at
12
+ end
13
+
14
+ class Comment < Sequel::Model
15
+ plugin :timestamps
16
+
17
+ many_to_one :author
18
+ many_to_one :post
19
+
20
+ def validate
21
+ super
22
+ validates_not_null [:author, :post]
23
+ end
24
+ end
25
+
26
+ class CommentSerializer < BaseSerializer
27
+ attribute :body
28
+
29
+ has_one :author
30
+ has_one :post
31
+ end
32
+
33
+ CommentController = proc do
34
+ helpers do
35
+ def find(id)
36
+ Comment[id.to_i]
37
+ end
38
+
39
+ def role
40
+ if resource&.author == current_user
41
+ super.push(:owner)
42
+ else
43
+ super
44
+ end
45
+ end
46
+ end
47
+
48
+ show do |id|
49
+ next find(id), include: 'author'
50
+ end
51
+
52
+ create(roles: :logged_in) do |attr|
53
+ comment = Comment.new
54
+ comment.set_fields(attr, %i[body])
55
+ comment.save(validate: false)
56
+ next_pk comment
57
+ end
58
+
59
+ update(roles: %i[owner superuser]) do |attr|
60
+ resource.update_fields(attr, %i[body], validate: false, missing: :skip)
61
+ end
62
+
63
+ destroy(roles: %i[owner superuser]) do
64
+ resource.destroy
65
+ end
66
+
67
+ has_one :post do
68
+ pluck do
69
+ resource.post
70
+ end
71
+
72
+ graft(roles: :superuser, sideload_on: :create) do |rio|
73
+ resource.post = Post.with_pk!(rio[:id].to_i)
74
+ resource.save_changes(validate: !sideloaded?)
75
+ end
76
+ end
77
+
78
+ has_one :author do
79
+ pluck do
80
+ resource.author
81
+ end
82
+
83
+ graft(roles: :superuser, sideload_on: :create) do |rio|
84
+ halt 403, 'You may only assign yourself as comment author!' \
85
+ unless role?(:superuser) || rio[:id].to_i == current_user.id
86
+
87
+ resource.author = Author.with_pk!(rio[:id].to_i)
88
+ resource.save_changes(validate: !sideloaded?)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../base'
3
+ require_relative '../database'
4
+
5
+ DB.create_table?(:posts) do
6
+ String :slug, :primary_key=>true
7
+ foreign_key :author_id, :authors, :on_delete=>:cascade
8
+ String :title, :null=>false
9
+ String :body, :text=>true, :null=>false
10
+ DateTime :created_at
11
+ DateTime :updated_at
12
+ end
13
+
14
+ class Post < Sequel::Model
15
+ plugin :timestamps
16
+
17
+ unrestrict_primary_key
18
+
19
+ many_to_one :author
20
+ one_to_many :comments
21
+ many_to_many :tags, :left_key=>:post_slug
22
+
23
+ def validate
24
+ super
25
+ validates_not_null :author
26
+ end
27
+ end
28
+
29
+ class PostSerializer < BaseSerializer
30
+ def id
31
+ object.slug
32
+ end
33
+
34
+ attributes :title, :body
35
+
36
+ has_one :author
37
+ has_many :comments
38
+ has_many :tags
39
+ end
40
+
41
+ PostController = proc do
42
+ helpers do
43
+ def find(slug)
44
+ Post[slug.to_s]
45
+ end
46
+
47
+ def role
48
+ if resource&.author == current_user
49
+ super.push(:owner)
50
+ else
51
+ super
52
+ end
53
+ end
54
+ end
55
+
56
+ show do |slug|
57
+ next find(slug), include: %w[author comments tags]
58
+ end
59
+
60
+ index do
61
+ # TODO: Filter/sort by created_at and/or updated_at?
62
+ Post.all
63
+ end
64
+
65
+ create(roles: :logged_in) do |attr, slug|
66
+ post = Post.new
67
+ post.set_fields(attr, %i[title body])
68
+ post.slug = slug.to_s # set primary key
69
+ post.save(validate: false)
70
+ next_pk post
71
+ end
72
+
73
+ update(roles: %i[owner superuser]) do |attr|
74
+ resource.update_fields(attr, %i[title body], validate: false, missing: :skip)
75
+ end
76
+
77
+ destroy(roles: %i[owner superuser]) do
78
+ resource.destroy
79
+ end
80
+
81
+ has_one :author do
82
+ pluck do
83
+ resource.author
84
+ end
85
+
86
+ graft(roles: :superuser, sideload_on: :create) do |rio|
87
+ halt 403, 'You may only assign yourself as post author!' \
88
+ unless role?(:superuser) || rio[:id].to_i == current_user.id
89
+
90
+ resource.author = Author.with_pk!(rio[:id].to_i)
91
+ resource.save_changes(validate: !sideloaded?)
92
+ end
93
+ end
94
+
95
+ has_many :comments do
96
+ fetch do
97
+ next resource.comments, include: 'author'
98
+ end
99
+ end
100
+
101
+ has_many :tags do
102
+ fetch do
103
+ resource.tags
104
+ end
105
+
106
+ merge(roles: %i[owner superuser], sideload_on: %i[create update]) do |rios|
107
+ add_missing(:tags, rios)
108
+ end
109
+
110
+ subtract(roles: %i[owner superuser]) do |rios|
111
+ remove_present(:tags, rios)
112
+ end
113
+
114
+ clear(roles: %i[owner superuser], sideload_on: :update) do
115
+ resource.remove_all_tags
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../base'
3
+ require_relative '../database'
4
+
5
+ require_relative 'post' # make sure we create the posts table before the join table
6
+
7
+ DB.create_table?(:tags) do
8
+ primary_key :id
9
+ String :name, :null=>false, :unique=>true
10
+ end
11
+
12
+ DB.create_table?(:posts_tags) do
13
+ foreign_key :post_slug, :posts, :null=>false, :on_delete=>:cascade, :type=>String
14
+ foreign_key :tag_id, :tags, :null=>false, :on_delete=>:cascade
15
+ primary_key [:post_slug, :tag_id]
16
+ index [:tag_id, :post_slug]
17
+ end
18
+
19
+ class Tag < Sequel::Model
20
+ many_to_many :posts
21
+ end
22
+
23
+ class TagSerializer < BaseSerializer
24
+ attribute :name
25
+
26
+ has_many :posts
27
+ end
28
+
29
+ TagController = proc do
30
+ helpers do
31
+ def find(id)
32
+ Tag[id.to_i]
33
+ end
34
+ end
35
+
36
+ show
37
+
38
+ index do
39
+ Tag.all
40
+ end
41
+
42
+ create(roles: :logged_in) do |attr|
43
+ tag = Tag.new
44
+ tag.set_fields(attr, %i[name])
45
+ tag.save(validate: false)
46
+ next_pk tag
47
+ end
48
+
49
+ destroy(roles: :superuser) do
50
+ resource.destroy
51
+ end
52
+
53
+ has_many :posts do
54
+ fetch do
55
+ resource.posts
56
+ end
57
+
58
+ merge(roles: :logged_in) do |rios|
59
+ add_missing(:posts, rios) do |post|
60
+ role?(:superuser) || post.author == current_user
61
+ end
62
+ end
63
+
64
+ subtract(roles: :logged_in) do |rios|
65
+ remove_present(:posts, rios) do |post|
66
+ role?(:superuser) || post.author == current_user
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ require 'logger'
3
+ require_relative 'boot'
4
+
5
+ DB =
6
+ if defined?(JRUBY_VERSION)
7
+ Sequel.connect 'jdbc:sqlite::memory:'
8
+ else
9
+ Sequel.sqlite
10
+ end
11
+
12
+ DB.loggers << Logger.new($stderr) if Sinatra::Base.development?