days 0.0.1.earlier → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +9 -0
  4. data/README.md +2 -0
  5. data/app/images/glyphicons-halflings-white.png +0 -0
  6. data/app/images/glyphicons-halflings.png +0 -0
  7. data/app/javascripts/bootstrap.js +2159 -0
  8. data/app/javascripts/bootstrap.min.js +6 -0
  9. data/app/javascripts/jquery-1.8.3.min.js +2 -0
  10. data/app/stylesheets/admin/login.scss +26 -0
  11. data/app/stylesheets/admin.scss +28 -0
  12. data/app/stylesheets/bootstrap-responsive.css +1092 -0
  13. data/app/stylesheets/bootstrap-responsive.min.css +9 -0
  14. data/app/stylesheets/bootstrap.css +6039 -0
  15. data/app/stylesheets/bootstrap.min.css +9 -0
  16. data/app/stylesheets/style.scss +132 -0
  17. data/app/views/admin/categories.haml +53 -0
  18. data/app/views/admin/entries/form.haml +55 -0
  19. data/app/views/admin/entries/index.haml +31 -0
  20. data/app/views/admin/index.haml +0 -0
  21. data/app/views/admin/login.haml +19 -0
  22. data/app/views/admin/setup.haml +22 -0
  23. data/app/views/admin/users/form.haml +25 -0
  24. data/app/views/admin/users/index.haml +23 -0
  25. data/app/views/admin.haml +37 -0
  26. data/app/views/entries.haml +4 -0
  27. data/app/views/entry.haml +32 -0
  28. data/app/views/layout.haml +32 -0
  29. data/bin/days +5 -0
  30. data/bootstrap.sh +41 -0
  31. data/days.gemspec +25 -2
  32. data/lib/days/app/admin/categories.rb +43 -0
  33. data/lib/days/app/admin/entries.rb +72 -0
  34. data/lib/days/app/admin/session.rb +28 -0
  35. data/lib/days/app/admin/setup.rb +20 -0
  36. data/lib/days/app/admin/users.rb +59 -0
  37. data/lib/days/app/admin.rb +11 -0
  38. data/lib/days/app/entries.rb +84 -0
  39. data/lib/days/app.rb +110 -0
  40. data/lib/days/command.rb +158 -0
  41. data/lib/days/config.rb +42 -0
  42. data/lib/days/helpers.rb +101 -0
  43. data/lib/days/migrate/20121221000000_create_entries.rb +18 -0
  44. data/lib/days/migrate/20121221001000_create_users.rb +12 -0
  45. data/lib/days/migrate/20121221002000_create_categories.rb +17 -0
  46. data/lib/days/migrator.rb +33 -0
  47. data/lib/days/models/category.rb +12 -0
  48. data/lib/days/models/entry.rb +63 -0
  49. data/lib/days/models/user.rb +14 -0
  50. data/lib/days/models.rb +7 -0
  51. data/lib/days/version.rb +1 -1
  52. data/lib/days.rb +3 -1
  53. data/scripts/lokka_export.rb +45 -0
  54. data/skeleton/days/Gemfile +11 -0
  55. data/skeleton/days/config.ru +6 -0
  56. data/skeleton/days/config.yml +33 -0
  57. data/skeleton/days/db/.gitkeep +0 -0
  58. data/spec/controllers/admin/categories_spec.rb +100 -0
  59. data/spec/controllers/admin/entries_spec.rb +185 -0
  60. data/spec/controllers/admin/session_spec.rb +112 -0
  61. data/spec/controllers/admin/setup_spec.rb +85 -0
  62. data/spec/controllers/admin/users_spec.rb +163 -0
  63. data/spec/controllers/entries_spec.rb +129 -0
  64. data/spec/environment/Gemfile +11 -0
  65. data/spec/environment/config.ru +6 -0
  66. data/spec/environment/config.yml +32 -0
  67. data/spec/environment/db/.gitkeep +0 -0
  68. data/spec/fixtures/categories.yml +5 -0
  69. data/spec/fixtures/entries.yml +25 -0
  70. data/spec/fixtures/users.yml +6 -0
  71. data/spec/helpers_spec.rb +117 -0
  72. data/spec/models/entry_spec.rb +238 -0
  73. data/spec/shared/admin.rb +8 -0
  74. data/spec/spec_helper.rb +134 -0
  75. data/tasks +9 -0
  76. metadata +353 -9
@@ -0,0 +1,158 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+ require_relative 'config'
4
+ require_relative 'app'
5
+ require_relative 'migrator'
6
+
7
+ module Days
8
+ class Command < Thor
9
+ SKELETON_PATH = File.expand_path(File.join(__FILE__, '..', '..', '..', 'skeleton', 'days'))
10
+ desc "init [DIR]", "Initialize days.gem world in the directory (default = ./)"
11
+ def init(dir = ".")
12
+ puts "Initializing new Days environment on #{File.expand_path dir}"
13
+ FileUtils.cp_r Dir["#{SKELETON_PATH}/*"], "#{dir}/"
14
+ end
15
+
16
+ desc "init_theme [DIR]", "Generate template of theme (views) to DIR (default = ./)"
17
+ def init_theme(dir = ".")
18
+ if File.exists?(File.join(dir, 'views')) || File.exists?(File.join(dir, 'stylesheets'))
19
+ puts "This will override the following:"
20
+ puts "* #{dir}/views/entries.haml"
21
+ puts "* #{dir}/views/entry.haml"
22
+ puts "* #{dir}/views/layout.haml"
23
+ puts "* #{dir}/stylesheets/style.scss"
24
+
25
+ print "Continue (y/n)? "
26
+
27
+ while _ = $stdin.gets
28
+ case _
29
+ when /^y|yes$/
30
+ break
31
+ when /^n|no$/
32
+ puts 'Cancelled.'
33
+ return
34
+ else
35
+ print "Please answer in 'yes' or 'no' or 'y' or 'n': "
36
+ end
37
+ end
38
+ end
39
+
40
+ require 'fileutils'
41
+
42
+ root = File.expand_path(File.join(__FILE__, '..', '..', '..', 'app'))
43
+ FileUtils.mkdir_p File.join(dir, 'views')
44
+ FileUtils.mkdir_p File.join(dir, 'stylesheets')
45
+
46
+ %w(entries.haml entry.haml layout.haml).each do |file|
47
+ FileUtils.cp(File.join(root, 'views', file),
48
+ File.join(dir, 'views', file))
49
+ end
50
+
51
+ FileUtils.cp(File.join(root, 'stylesheets', 'style.scss'),
52
+ File.join(dir, 'stylesheets', 'style.scss'))
53
+ end
54
+
55
+ desc "server", "Starts the server"
56
+ method_option :config, :type => :string, :aliases => "-c"
57
+ method_option :port, :type => :numeric, :aliases => "-p", :default => 3162
58
+ method_option :bind, :type => :string, :aliases => "-b", :default => nil
59
+ method_option :environment, :type => :string, :aliases => "-e", :default => "development"
60
+ method_option :pid, :type => :string, :aliases => "-c"
61
+ def server
62
+ set_env
63
+ App.config = config
64
+ rack_options = {
65
+ app: App.rack,
66
+ Port: options[:port],
67
+ Host: options[:bind],
68
+ daemonize: options[:daemonize],
69
+ environment: options[:environment],
70
+ server: options[:server],
71
+ }
72
+ rack_options.merge!(pid: File.expand_path(options[:pid])) if options[:pid]
73
+ Rack::Server.start(rack_options)
74
+ end
75
+
76
+ # desc "precompile", "Precompile the assets for production"
77
+ # method_option :config, :type => :string, :aliases => "-c"
78
+ # def precompile
79
+ # end
80
+
81
+ desc "migrate [ENV]", "Run database migration for environment (default = development)"
82
+ method_option :config, :type => :string, :aliases => "-c"
83
+ method_option :version, :type => :numeric, :aliases => "-v", :default => nil
84
+ method_option :scope, :type => :string, :aliases => "-s"
85
+ method_option :verbose, :type => :boolean, :aliases => "-V", :default => true
86
+ def migrate(env = "development")
87
+ set_env env
88
+ Days::Migrator.start(config, options)
89
+ end
90
+
91
+ desc "console [ENV]", "Start console using pry (default = development)"
92
+ method_option :config, :type => :string, :aliases => "-c"
93
+ def console(env = "development")
94
+ set_env env
95
+ require 'pry'
96
+ require_relative 'models'
97
+ config.establish_db_connection()
98
+ Pry.start(binding)
99
+ end
100
+
101
+ desc "import FILE", "Import entries from file."
102
+ method_option :config, :type => :string, :aliases => "-c"
103
+ method_option :environment, :type => :string, :aliases => "-e", :default => "development"
104
+ def import(file)
105
+ set_env
106
+ config.establish_db_connection()
107
+ require 'json'
108
+ users = {}
109
+ categories = {}
110
+ open(file, 'r') do |io|
111
+ io.readlines.each do |line|
112
+ line = JSON.parse(line)
113
+
114
+ attributes ={}
115
+ if Entry.where(id: line['id']).count.zero?
116
+ attributes[:id] = line['id']
117
+ end
118
+
119
+ if line['user']
120
+ if users.has_key?(line['user'])
121
+ user = users[line['user']]
122
+ else
123
+ user = users[line['user']] = User.where(login_name: line['user']).first
124
+ end
125
+
126
+
127
+ attributes[:user] = user if user
128
+ end
129
+
130
+ if line['category']
131
+ attributes[:categories] = line['category'].map do |category_name|
132
+ categories[category_name] ||= Category.find_or_create_by_name!(category_name)
133
+ end
134
+ end
135
+
136
+ attributes[:slug] = line['slug'] if line['slug']
137
+ attributes[:title] = line['title'] if line['title']
138
+ attributes[:body] = line['body'] if line['body']
139
+ attributes[:published_at] = line['published_at'] if line['published_at']
140
+ attributes[:draft] = line['draft'] if line['draft']
141
+
142
+ p attributes[:title]
143
+ Entry.create!(attributes)
144
+ end
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def set_env(env = nil)
151
+ App.environment = env || options[:environment] || ENV["RACK_ENV"] || :development
152
+ end
153
+
154
+ def config
155
+ @_days_config ||= Config.new(options[:config] || "./config.yml")
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'app'
2
+ require 'active_record'
3
+ require 'logger'
4
+ require 'settingslogic'
5
+
6
+ module Days
7
+ class Config < Settingslogic
8
+ namespace App.environment.to_s
9
+
10
+ def initialize(hash_or_file = {}, section = nil)
11
+ super
12
+ if section.nil?
13
+ if String === hash_or_file
14
+ self[:root] = File.dirname(hash_or_file)
15
+ else
16
+ self[:root] = "."
17
+ end
18
+
19
+ self['database'].tap do |hash|
20
+ next unless hash
21
+ if hash['adapter'] == 'sqlite3' && /^\// !~ hash['database']
22
+ hash['database'] = File.join(self.root, hash['database'])
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def establish_db_connection(force=false)
29
+ if !self.has_key?(:activerecord_log) || self.activerecord_log == true
30
+ ActiveRecord::Base.logger = Logger.new($stdout)
31
+ end
32
+
33
+ begin
34
+ raise ActiveRecord::ConnectionNotEstablished if force
35
+ return ActiveRecord::Base.connection
36
+ rescue ActiveRecord::ConnectionNotEstablished
37
+ ActiveRecord::Base.establish_connection(self['database'] ? Hash[self.database] : ENV["DATABASE_URL"])
38
+ retry
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,101 @@
1
+ require 'rack/csrf'
2
+
3
+ module Days
4
+ module Helpers
5
+ def config
6
+ Days::App.config
7
+ end
8
+
9
+ def logged_in?
10
+ !!session[:user_id]
11
+ end
12
+
13
+ def current_user
14
+ @current_user ||= session[:user_id] ? User.where(id: session[:user_id]).first : nil
15
+ end
16
+
17
+ def csrf_token
18
+ Rack::Csrf.csrf_token(env)
19
+ end
20
+
21
+ def csrf_tag
22
+ Rack::Csrf.csrf_tag(env)
23
+ end
24
+
25
+ def entry_path(entry)
26
+ return nil unless entry.published?
27
+
28
+ published_at = entry.published_at
29
+ hash = {
30
+ year: published_at.year.to_s.rjust(2, '0'),
31
+ month: published_at.month.to_s.rjust(2, '0'),
32
+ day: published_at.day.to_s.rjust(2, '0'),
33
+ hour: published_at.hour.to_s.rjust(2, '0'),
34
+ minute: published_at.min.to_s.rjust(2, '0'),
35
+ second: published_at.sec.to_s.rjust(2, '0'),
36
+ slug: entry.slug, id: entry.id
37
+ }
38
+ config.permalink.gsub(/{(\w+?)}/) { hash[$1.to_sym] }
39
+ end
40
+
41
+ def lookup_entry(path)
42
+ regexp = Regexp.compile(Regexp.escape(config.permalink).gsub(/\\{(\w+?)\\}/) { "(?<#{$1}>.+?)" } + "$")
43
+ m = regexp.match(path)
44
+ return nil unless m
45
+
46
+ match = m.names.inject({}) do |hash, k|
47
+ hash[k.to_sym] = m[k]
48
+ hash
49
+ end
50
+
51
+
52
+ if match[:id] || match[:slug]
53
+ if match[:id]
54
+ query = Entry.where(id: match[:id])
55
+ else
56
+ query = Entry.where(slug: match[:slug])
57
+ end
58
+
59
+ entry = query.first
60
+ return nil unless entry
61
+ published_at = entry.published_at
62
+ return nil unless published_at
63
+
64
+ return nil if match[:slug] && match[:slug] != entry.slug
65
+ return nil if match[:id] && match[:id].to_i != entry.id
66
+ return nil if match[:year] && match[:year].to_i != published_at.year
67
+ return nil if match[:month] && match[:month].to_i != published_at.month
68
+ return nil if match[:day] && match[:day].to_i != published_at.day
69
+ return nil if match[:hour] && match[:hour].to_i != published_at.hour
70
+ return nil if match[:minute] && match[:minute].to_i != published_at.min
71
+ return nil if match[:second] && match[:second].to_i != published_at.sec
72
+
73
+ return entry
74
+ else
75
+ match_time = {}.tap do |h|
76
+ [:year, :month, :day, :hour, :minute, :second].each do |k|
77
+ h[k] = match[k] && match[k].to_i
78
+ end
79
+ end
80
+ range_begin = Time.local(
81
+ match_time[:year], match_time[:month] || 1, match_time[:day] || 1,
82
+ match_time[:hour] || 0, match_time[:minute] || 0, match_time[:second] || 0
83
+ )
84
+ range_end = Time.local(
85
+ match_time[:year], match_time[:month] || 12, match_time[:day] || 31,
86
+ match_time[:hour] || 23, match_time[:minute] || 59, match_time[:second] || 59
87
+ )
88
+
89
+ query = Entry.where(published_at: (range_begin .. range_end))
90
+
91
+ if query.count.zero?
92
+ return nil
93
+ elsif query.count == 1
94
+ return query.first
95
+ else
96
+ return query
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,18 @@
1
+ class CreateEntries < ActiveRecord::Migration
2
+ def change
3
+ create_table :entries do |t|
4
+ t.string :title
5
+ t.text :body
6
+ t.text :rendered
7
+ t.datetime :published_at
8
+ t.integer :user_id
9
+ t.string :slug
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :entries, :published_at
15
+ add_index :entries, :user_id
16
+ add_index :entries, :slug
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def change
3
+ create_table :users do |t|
4
+ t.string :login_name
5
+ t.binary :password_digest
6
+
7
+ t.string :name
8
+ end
9
+
10
+ add_index :users, :login_name
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ class CreateCategories < ActiveRecord::Migration
2
+ def change
3
+ create_table :categories do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :categories_entries do |t|
8
+ t.integer :category_id
9
+ t.integer :entry_id
10
+ end
11
+
12
+ add_index :categories, :name
13
+
14
+ add_index :categories_entries, :entry_id
15
+ add_index :categories_entries, :category_id
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module Days
2
+ module Migrator
3
+ def self.start(config, options = {})
4
+ require 'active_record'
5
+ require 'active_support/core_ext/class/attribute_accessors.rb'
6
+ require 'active_record/schema_dumper'
7
+
8
+ config.establish_db_connection()
9
+ orig_logger = ActiveRecord::Base.logger
10
+ begin
11
+ ActiveRecord::Base.logger = nil unless options[:show_sql]
12
+ ActiveRecord::Migration.verbose = options[:verbose]
13
+
14
+ migration_paths = [
15
+ config[:migration_path] || "#{config.root}/db/migrate",
16
+ File.expand_path(File.join(__FILE__, '..', 'migrate'))
17
+ ]
18
+ ActiveRecord::Migrator.migrate(migration_paths, options[:version]) do |migration|
19
+ options[:scope].blank? || (options[:scope] == migration.scope)
20
+ end
21
+
22
+ schema_file = config[:schema] || "#{config.root}/db/schema.rb"
23
+ File.open(schema_file, "w:utf-8") do |io|
24
+ ActiveRecord::SchemaDumper.dump ActiveRecord::Base.connection, io
25
+ end
26
+
27
+ self
28
+ ensure
29
+ ActiveRecord::Base.logger = orig_logger
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_record'
2
+
3
+ module Days
4
+ class Category < ActiveRecord::Base
5
+ attr_accessible :name
6
+
7
+ validates_presence_of :name
8
+ validates_uniqueness_of :name
9
+
10
+ has_and_belongs_to_many :entries, class_name: 'Days::Entry'
11
+ end
12
+ end
@@ -0,0 +1,63 @@
1
+ require 'active_record'
2
+ require 'stringex'
3
+ require 'redcarpet'
4
+
5
+ module Days
6
+ class Entry < ActiveRecord::Base
7
+ attr_accessible :title, :body, :slug, :published_at, :categories, :user, :draft
8
+
9
+ validates_uniqueness_of :slug
10
+ validates_presence_of :title, :body, :rendered, :slug
11
+
12
+ belongs_to :user, class_name: 'Days::User'
13
+ has_and_belongs_to_many :categories, class_name: 'Days::Category'
14
+
15
+ scope :published, -> do
16
+ includes(:categories).
17
+ where('published_at IS NOT NULL AND published_at <= ?', Time.now).
18
+ order('published_at DESC')
19
+ end
20
+
21
+ def draft=(x)
22
+ if x.present?
23
+ @draft = true
24
+ else
25
+ @draft = false
26
+ end
27
+ end
28
+
29
+ def draft?
30
+ @draft
31
+ end
32
+ alias draft draft?
33
+
34
+ def published?
35
+ self.published_at && self.published_at <= Time.now
36
+ end
37
+
38
+ def scheduled?
39
+ self.published_at && Time.now < self.published_at
40
+ end
41
+
42
+ def short_rendered
43
+ self.rendered.gsub(/<!-- *more *-->.+\z/m, block_given? ? yield(self) : '')
44
+ end
45
+
46
+ before_validation do
47
+ if draft?
48
+ self.published_at = nil
49
+ else
50
+ self.published_at ||= Time.now
51
+ end
52
+
53
+ if self.title && (!self.slug || self.slug.empty?)
54
+ self.slug = self.title.to_url
55
+ end
56
+ markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML,
57
+ :autolink => true, :space_after_headers => true,
58
+ :no_intra_emphasis => true, :fenced_code_blocks => true,
59
+ :tables => true, :superscript => true)
60
+ self.rendered = markdown.render(self.body)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_record'
2
+
3
+ module Days
4
+ class User < ActiveRecord::Base
5
+ has_secure_password
6
+
7
+ attr_accessible :login_name, :name, :password, :password_confirmation
8
+
9
+ validates_uniqueness_of :login_name
10
+ validates_presence_of :login_name, :name
11
+
12
+ has_many :entries, class_name: 'Days::Entry'
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ %w[
2
+ user
3
+ category
4
+ entry
5
+ ].each do |m|
6
+ require "days/models/#{m}"
7
+ end
data/lib/days/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Days
2
- VERSION = "0.0.1.earlier"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/days.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require "days/version"
2
+ require "days/config"
3
+ require "days/models"
4
+ require "days/app"
2
5
 
3
6
  module Days
4
- # Your code goes here...
5
7
  end
@@ -0,0 +1,45 @@
1
+ require 'active_record'
2
+ require 'json'
3
+
4
+ ActiveRecord::Base.establish_connection ENV["DATABASE_URL"]
5
+
6
+ class User < ActiveRecord::Base
7
+ has_many :entries
8
+ end
9
+
10
+ class Category < ActiveRecord::Base
11
+ has_many :entries
12
+ end
13
+
14
+ class Entry < ActiveRecord::Base
15
+ belongs_to :user
16
+ belongs_to :category
17
+ end
18
+
19
+ class Post < Entry
20
+ end
21
+
22
+ Post.order('id ASC').all.each do |post|
23
+ if 'kramdown' != post.markup
24
+ warn "WARN: #{post.slug} (#{post.id}) is not written in kramdown"
25
+ end
26
+
27
+ hash = {}
28
+ hash[:id] = post.id
29
+ hash[:user] = post.user.name
30
+ hash[:slug] = post.slug
31
+ hash[:title] = post.title
32
+ hash[:body] = post.body
33
+ if post.draft == 1
34
+ hash[:published_at] = post.created_at
35
+ else
36
+ hash[:published_at] = nil
37
+ hash[:draft] = true
38
+ end
39
+ if post.category
40
+ hash[:category] = [post.category.title]
41
+ else
42
+ hash[:category] = []
43
+ end
44
+ puts hash.to_json
45
+ end
@@ -0,0 +1,11 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "days"
4
+
5
+ group :production do
6
+ # gem "mysql2"
7
+ end
8
+
9
+ group :development do
10
+ gem "sqlite3"
11
+ end
@@ -0,0 +1,6 @@
1
+ require 'bundler'
2
+ require 'days'
3
+ Bundler.require(:default, Days::App.environment)
4
+
5
+ Days::App.set :config, Days::Config.new("#{File.dirname(__FILE__)}/config.yml")
6
+ run Days::App.rack
@@ -0,0 +1,33 @@
1
+ defaults: &defaults
2
+ base_path: "/"
3
+ permalink: "/{year}/{month}/{slug}"
4
+ title: "My Blog"
5
+
6
+ development:
7
+ <<: *defaults
8
+ database:
9
+ adapter: sqlite3
10
+ database: db/development.sqlite3
11
+ pool: 5
12
+ timeout: 5000
13
+
14
+ test:
15
+ <<: *defaults
16
+ database:
17
+ adapter: sqlite3
18
+ database: db/test.sqlite3
19
+ pool: 5
20
+ timeout: 5000
21
+
22
+ production:
23
+ <<: *defaults
24
+ <% unless ENV["DATABASE_URL"] %>
25
+ database:
26
+ adapter: mysql2
27
+ encoding: utf8
28
+ database: days_production
29
+ pool: 5
30
+ username: foo
31
+ password: bar
32
+ socket: /tmp/mysql.sock
33
+ <% end %>
File without changes