sinja 0.2.0.beta2 → 1.0.0.pre1

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.
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?