mirror of
https://github.com/TracksApp/tracks.git
synced 2026-01-25 10:16:11 +01:00
Removed superfluous 'tracks' directory at the root of the repository.
Testing commits to github.
This commit is contained in:
parent
6a42901514
commit
4cbf5a34d3
2269 changed files with 0 additions and 0 deletions
63
app/models/context.rb
Normal file
63
app/models/context.rb
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
class Context < ActiveRecord::Base
|
||||
|
||||
has_many :todos, :dependent => :delete_all, :include => :project, :order => "todos.completed_at DESC"
|
||||
belongs_to :user
|
||||
|
||||
acts_as_list :scope => :user
|
||||
extend NamePartFinder
|
||||
include Tracks::TodoList
|
||||
|
||||
attr_protected :user
|
||||
|
||||
validates_presence_of :name, :message => "context must have a name"
|
||||
validates_length_of :name, :maximum => 255, :message => "context name must be less than 256 characters"
|
||||
validates_uniqueness_of :name, :message => "already exists", :scope => "user_id"
|
||||
validates_does_not_contain :name, :string => ',', :message => "cannot contain the comma (',') character"
|
||||
|
||||
def self.feed_options(user)
|
||||
{
|
||||
:title => 'Tracks Contexts',
|
||||
:description => "Lists all the contexts for #{user.display_name}"
|
||||
}
|
||||
end
|
||||
|
||||
def self.null_object
|
||||
NullContext.new
|
||||
end
|
||||
|
||||
def hidden?
|
||||
self.hide == true || self.hide == 1
|
||||
end
|
||||
|
||||
def title
|
||||
name
|
||||
end
|
||||
|
||||
def summary(undone_todo_count)
|
||||
s = "<p>#{undone_todo_count}. "
|
||||
s += "Context is #{hidden? ? 'Hidden' : 'Active'}."
|
||||
s += "</p>"
|
||||
s
|
||||
end
|
||||
|
||||
def new_record_before_save?
|
||||
@new_record_before_save
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class NullContext
|
||||
|
||||
def nil?
|
||||
true
|
||||
end
|
||||
|
||||
def id
|
||||
nil
|
||||
end
|
||||
|
||||
def name
|
||||
''
|
||||
end
|
||||
|
||||
end
|
||||
7
app/models/note.rb
Normal file
7
app/models/note.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class Note < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :project
|
||||
|
||||
attr_protected :user
|
||||
|
||||
end
|
||||
30
app/models/preference.rb
Normal file
30
app/models/preference.rb
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
class Preference < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
composed_of :tz,
|
||||
:class_name => 'TimeZone',
|
||||
:mapping => %w(time_zone name)
|
||||
|
||||
def self.due_styles
|
||||
{ :due_in_n_days => 0, :due_on => 1}
|
||||
end
|
||||
|
||||
def self.day_number_to_name_map
|
||||
{ 0 => "Sunday",
|
||||
1 => "Monday",
|
||||
2 => "Tuesday",
|
||||
3 => "Wednesday",
|
||||
4 => "Thursday",
|
||||
5 => "Friday",
|
||||
6 => "Saturday"}
|
||||
end
|
||||
|
||||
def hide_completed_actions?
|
||||
return show_number_completed == 0
|
||||
end
|
||||
|
||||
def parse_date(s)
|
||||
return nil if s.blank?
|
||||
Date.strptime(s, date_format)
|
||||
end
|
||||
|
||||
end
|
||||
114
app/models/project.rb
Normal file
114
app/models/project.rb
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
class Project < ActiveRecord::Base
|
||||
has_many :todos, :dependent => :delete_all, :include => :context
|
||||
has_many :notes, :dependent => :delete_all, :order => "created_at DESC"
|
||||
belongs_to :default_context, :dependent => :nullify, :class_name => "Context", :foreign_key => "default_context_id"
|
||||
belongs_to :user
|
||||
|
||||
validates_presence_of :name, :message => "project must have a name"
|
||||
validates_length_of :name, :maximum => 255, :message => "project name must be less than 256 characters"
|
||||
validates_uniqueness_of :name, :message => "already exists", :scope =>"user_id"
|
||||
validates_does_not_contain :name, :string => ',', :message => "cannot contain the comma (',') character"
|
||||
|
||||
acts_as_list :scope => 'user_id = #{user_id} AND state = \'#{state}\''
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
extend NamePartFinder
|
||||
include Tracks::TodoList
|
||||
|
||||
state :active
|
||||
state :hidden, :enter => :hide_todos, :exit => :unhide_todos
|
||||
state :completed, :enter => Proc.new { |p| p.completed_at = Time.now.utc }, :exit => Proc.new { |p| p.completed_at = nil }
|
||||
|
||||
event :activate do
|
||||
transitions :to => :active, :from => [:hidden, :completed]
|
||||
end
|
||||
|
||||
event :hide do
|
||||
transitions :to => :hidden, :from => [:active, :completed]
|
||||
end
|
||||
|
||||
event :complete do
|
||||
transitions :to => :completed, :from => [:active, :hidden]
|
||||
end
|
||||
|
||||
attr_protected :user
|
||||
attr_accessor :cached_note_count
|
||||
|
||||
def self.null_object
|
||||
NullProject.new
|
||||
end
|
||||
|
||||
def self.feed_options(user)
|
||||
{
|
||||
:title => 'Tracks Projects',
|
||||
:description => "Lists all the projects for #{user.display_name}"
|
||||
}
|
||||
end
|
||||
|
||||
def hide_todos
|
||||
todos.each do |t|
|
||||
unless t.completed? || t.deferred?
|
||||
t.hide!
|
||||
t.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unhide_todos
|
||||
todos.each do |t|
|
||||
if t.project_hidden?
|
||||
t.unhide!
|
||||
t.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def note_count
|
||||
cached_note_count || notes.count
|
||||
end
|
||||
|
||||
alias_method :original_default_context, :default_context
|
||||
|
||||
def default_context
|
||||
original_default_context.nil? ? Context.null_object : original_default_context
|
||||
end
|
||||
|
||||
# would prefer to call this method state=(), but that causes an endless loop
|
||||
# as a result of acts_as_state_machine calling state=() to update the attribute
|
||||
def transition_to(candidate_state)
|
||||
case candidate_state.to_sym
|
||||
when current_state
|
||||
return
|
||||
when :hidden
|
||||
hide!
|
||||
when :active
|
||||
activate!
|
||||
when :completed
|
||||
complete!
|
||||
end
|
||||
end
|
||||
|
||||
def name=(value)
|
||||
self[:name] = value.gsub(/\s{2,}/, " ").strip
|
||||
end
|
||||
|
||||
def new_record_before_save?
|
||||
@new_record_before_save
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class NullProject
|
||||
|
||||
def hidden?
|
||||
false
|
||||
end
|
||||
|
||||
def nil?
|
||||
true
|
||||
end
|
||||
|
||||
def id
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
11
app/models/tag.rb
Normal file
11
app/models/tag.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class Tag < ActiveRecord::Base
|
||||
has_many_polymorphs :taggables,
|
||||
:from => [:todos],
|
||||
:through => :taggings,
|
||||
:dependent => :destroy
|
||||
|
||||
def on(taggable, user)
|
||||
tagging = taggings.create :taggable => taggable, :user => user
|
||||
end
|
||||
|
||||
end
|
||||
11
app/models/tagging.rb
Normal file
11
app/models/tagging.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class Tagging < ActiveRecord::Base
|
||||
belongs_to :tag
|
||||
belongs_to :taggable, :polymorphic => true
|
||||
belongs_to :user
|
||||
|
||||
# def before_destroy
|
||||
# # disallow orphaned tags
|
||||
# # TODO: this doesn't seem to be working
|
||||
# tag.destroy if tag.taggings.count < 2
|
||||
# end
|
||||
end
|
||||
122
app/models/todo.rb
Normal file
122
app/models/todo.rb
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
class Todo < ActiveRecord::Base
|
||||
|
||||
belongs_to :context, :order => 'name'
|
||||
belongs_to :project
|
||||
belongs_to :user
|
||||
|
||||
STARRED_TAG_NAME = "starred"
|
||||
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
|
||||
# when entering active state, also remove completed_at date.
|
||||
# Looks like :exit of state completed is not run, see #679
|
||||
state :active, :enter => Proc.new { |t| t[:show_from], t.completed_at = nil, nil }
|
||||
state :project_hidden
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.now.utc }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
state :deferred
|
||||
|
||||
event :defer do
|
||||
transitions :to => :deferred, :from => [:active]
|
||||
end
|
||||
|
||||
event :complete do
|
||||
transitions :to => :completed, :from => [:active, :project_hidden, :deferred]
|
||||
end
|
||||
|
||||
event :activate do
|
||||
transitions :to => :active, :from => [:project_hidden, :completed, :deferred]
|
||||
end
|
||||
|
||||
event :hide do
|
||||
transitions :to => :project_hidden, :from => [:active, :deferred]
|
||||
end
|
||||
|
||||
event :unhide do
|
||||
transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? }
|
||||
transitions :to => :active, :from => [:project_hidden]
|
||||
end
|
||||
|
||||
attr_protected :user
|
||||
|
||||
# Description field can't be empty, and must be < 100 bytes
|
||||
# Notes must be < 60,000 bytes (65,000 actually, but I'm being cautious)
|
||||
validates_presence_of :description
|
||||
validates_length_of :description, :maximum => 100
|
||||
validates_length_of :notes, :maximum => 60000, :allow_nil => true
|
||||
validates_presence_of :show_from, :if => :deferred?
|
||||
validates_presence_of :context
|
||||
|
||||
def validate
|
||||
if !show_from.blank? && show_from < user.date
|
||||
errors.add("show_from", "must be a date in the future")
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_completion!
|
||||
saved = false
|
||||
if completed?
|
||||
saved = activate!
|
||||
else
|
||||
saved = complete!
|
||||
end
|
||||
return saved
|
||||
end
|
||||
|
||||
def show_from
|
||||
self[:show_from]
|
||||
end
|
||||
|
||||
def show_from=(date)
|
||||
activate! if deferred? && date.blank?
|
||||
defer! if active? && !date.blank? && date > user.date
|
||||
self[:show_from] = date
|
||||
end
|
||||
|
||||
alias_method :original_project, :project
|
||||
|
||||
def project
|
||||
original_project.nil? ? Project.null_object : original_project
|
||||
end
|
||||
|
||||
alias_method :original_set_initial_state, :set_initial_state
|
||||
|
||||
def set_initial_state
|
||||
if show_from && (show_from > user.date)
|
||||
write_attribute self.class.state_column, 'deferred'
|
||||
else
|
||||
original_set_initial_state
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :original_run_initial_state_actions, :run_initial_state_actions
|
||||
|
||||
def run_initial_state_actions
|
||||
#only run the initial state actions if the standard initial state hasn't been changed
|
||||
if self.class.initial_state.to_sym == current_state
|
||||
original_run_initial_state_actions
|
||||
end
|
||||
end
|
||||
|
||||
def self.feed_options(user)
|
||||
{
|
||||
:title => 'Tracks Actions',
|
||||
:description => "Actions for #{user.display_name}"
|
||||
}
|
||||
end
|
||||
|
||||
def starred?
|
||||
tags.any? {|tag| tag.name == STARRED_TAG_NAME}
|
||||
end
|
||||
|
||||
def toggle_star!
|
||||
if starred?
|
||||
delete_tags STARRED_TAG_NAME
|
||||
tags.reload
|
||||
else
|
||||
add_tag STARRED_TAG_NAME
|
||||
tags.reload
|
||||
end
|
||||
starred?
|
||||
end
|
||||
|
||||
end
|
||||
223
app/models/user.rb
Normal file
223
app/models/user.rb
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
require 'digest/sha1'
|
||||
|
||||
class User < ActiveRecord::Base
|
||||
# Virtual attribute for the unencrypted password
|
||||
attr_accessor :password
|
||||
|
||||
has_many :contexts,
|
||||
:order => 'position ASC',
|
||||
:dependent => :delete_all do
|
||||
def find_by_params(params)
|
||||
find(params['id'] || params['context_id']) || nil
|
||||
end
|
||||
end
|
||||
has_many :projects,
|
||||
:order => 'projects.position ASC',
|
||||
:dependent => :delete_all do
|
||||
def find_by_params(params)
|
||||
find(params['id'] || params['project_id'])
|
||||
end
|
||||
def update_positions(project_ids)
|
||||
project_ids.each_with_index do |id, position|
|
||||
project = self.detect { |p| p.id == id.to_i }
|
||||
raise "Project id #{id} not associated with user id #{@user.id}." if project.nil?
|
||||
project.update_attribute(:position, position + 1)
|
||||
end
|
||||
end
|
||||
def projects_in_state_by_position(state)
|
||||
self.sort{ |a,b| a.position <=> b.position }.select{ |p| p.state == state }
|
||||
end
|
||||
def next_from(project)
|
||||
self.offset_from(project, 1)
|
||||
end
|
||||
def previous_from(project)
|
||||
self.offset_from(project, -1)
|
||||
end
|
||||
def offset_from(project, offset)
|
||||
projects = self.projects_in_state_by_position(project.state)
|
||||
position = projects.index(project)
|
||||
return nil if position == 0 && offset < 0
|
||||
projects.at( position + offset)
|
||||
end
|
||||
def cache_note_counts
|
||||
project_note_counts = Note.count(:group => 'project_id')
|
||||
self.each do |project|
|
||||
project.cached_note_count = project_note_counts[project.id] || 0
|
||||
end
|
||||
end
|
||||
def alphabetize(scope_conditions = {})
|
||||
projects = find(:all, :conditions => scope_conditions)
|
||||
projects.sort!{ |x,y| x.name.downcase <=> y.name.downcase }
|
||||
self.update_positions(projects.map{ |p| p.id })
|
||||
return projects
|
||||
end
|
||||
end
|
||||
has_many :active_projects,
|
||||
:class_name => 'Project',
|
||||
:order => 'projects.position ASC',
|
||||
:conditions => [ 'state = ?', 'active' ]
|
||||
has_many :active_contexts,
|
||||
:class_name => 'Context',
|
||||
:order => 'position ASC',
|
||||
:conditions => [ 'hide = ?', 'true' ]
|
||||
has_many :todos,
|
||||
:order => 'todos.completed_at DESC, todos.created_at DESC',
|
||||
:dependent => :delete_all
|
||||
has_many :deferred_todos,
|
||||
:class_name => 'Todo',
|
||||
:conditions => [ 'state = ?', 'deferred' ],
|
||||
:order => 'show_from ASC, todos.created_at DESC' do
|
||||
def find_and_activate_ready
|
||||
find(:all, :conditions => ['show_from <= ?', proxy_owner.time ]).collect { |t| t.activate! }
|
||||
end
|
||||
end
|
||||
has_many :completed_todos,
|
||||
:class_name => 'Todo',
|
||||
:conditions => ['todos.state = ? and todos.completed_at is not null', 'completed'],
|
||||
:order => 'todos.completed_at DESC',
|
||||
:include => [ :project, :context ] do
|
||||
def completed_within( date )
|
||||
reject { |x| x.completed_at < date }
|
||||
end
|
||||
|
||||
def completed_more_than( date )
|
||||
reject { |x| x.completed_at > date }
|
||||
end
|
||||
end
|
||||
has_many :notes, :order => "created_at DESC", :dependent => :delete_all
|
||||
has_one :preference, :dependent => :destroy
|
||||
has_many :taggings
|
||||
has_many :tags, :through => :taggings, :select => "DISTINCT tags.*"
|
||||
|
||||
attr_protected :is_admin
|
||||
|
||||
validates_presence_of :login
|
||||
validates_presence_of :password, :if => :password_required?
|
||||
validates_length_of :password, :within => 5..40, :if => :password_required?
|
||||
validates_presence_of :password_confirmation, :if => :password_required?
|
||||
validates_confirmation_of :password
|
||||
validates_length_of :login, :within => 3..80
|
||||
validates_uniqueness_of :login, :on => :create
|
||||
validates_presence_of :open_id_url, :if => :using_openid?
|
||||
|
||||
before_create :crypt_password, :generate_token
|
||||
before_update :crypt_password
|
||||
before_save :normalize_open_id_url
|
||||
|
||||
def validate
|
||||
unless Tracks::Config.auth_schemes.include?(auth_type)
|
||||
errors.add("auth_type", "not a valid authentication type (#{auth_type})")
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :prefs, :preference
|
||||
|
||||
def self.authenticate(login, pass)
|
||||
return nil if login.blank?
|
||||
candidate = find(:first, :conditions => ["login = ?", login])
|
||||
return nil if candidate.nil?
|
||||
return candidate if candidate.auth_type == 'database' && candidate.crypted_password == sha1(pass)
|
||||
if Tracks::Config.auth_schemes.include?('ldap')
|
||||
return candidate if candidate.auth_type == 'ldap' && SimpleLdapAuthenticator.valid?(login, pass)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
def self.find_by_open_id_url(raw_open_id_url)
|
||||
normalized_open_id_url = normalize_open_id_url(raw_open_id_url)
|
||||
find(:first, :conditions => ['open_id_url = ?', normalized_open_id_url])
|
||||
end
|
||||
|
||||
def self.no_users_yet?
|
||||
count == 0
|
||||
end
|
||||
|
||||
def self.find_admin
|
||||
find(:first, :conditions => [ "is_admin = ?", true ])
|
||||
end
|
||||
|
||||
def to_param
|
||||
login
|
||||
end
|
||||
|
||||
def display_name
|
||||
if first_name.blank? && last_name.blank?
|
||||
return login
|
||||
elsif first_name.blank?
|
||||
return last_name
|
||||
elsif last_name.blank?
|
||||
return first_name
|
||||
end
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
def change_password(pass,pass_confirm)
|
||||
self.password = pass
|
||||
self.password_confirmation = pass_confirm
|
||||
save!
|
||||
end
|
||||
|
||||
def time
|
||||
prefs.tz.adjust(Time.now.utc)
|
||||
end
|
||||
|
||||
def date
|
||||
time.to_date
|
||||
end
|
||||
|
||||
def generate_token
|
||||
self.token = Digest::SHA1.hexdigest "#{Time.now.to_i}#{rand}"
|
||||
end
|
||||
|
||||
def remember_token?
|
||||
remember_token_expires_at && Time.now.utc < remember_token_expires_at
|
||||
end
|
||||
|
||||
# These create and unset the fields required for remembering users between browser closes
|
||||
def remember_me
|
||||
self.remember_token_expires_at = 2.weeks.from_now.utc
|
||||
self.remember_token = self.class.sha1("#{login}--#{remember_token_expires_at}")
|
||||
save(false)
|
||||
end
|
||||
|
||||
def forget_me
|
||||
self.remember_token_expires_at = nil
|
||||
self.remember_token = nil
|
||||
save(false)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.sha1(s)
|
||||
Digest::SHA1.hexdigest("#{Tracks::Config.salt}--#{s}--")
|
||||
end
|
||||
|
||||
def crypt_password
|
||||
return if password.blank?
|
||||
write_attribute("crypted_password", self.class.sha1(password)) if password == password_confirmation
|
||||
end
|
||||
|
||||
def password_required?
|
||||
auth_type == 'database' && crypted_password.blank? || !password.blank?
|
||||
end
|
||||
|
||||
def using_openid?
|
||||
auth_type == 'open_id'
|
||||
end
|
||||
|
||||
def password_matches?(pass)
|
||||
crypted_password == sha1(pass)
|
||||
end
|
||||
|
||||
def normalize_open_id_url
|
||||
return if open_id_url.nil?
|
||||
self.open_id_url = self.class.normalize_open_id_url(open_id_url)
|
||||
end
|
||||
|
||||
def self.normalize_open_id_url(raw_open_id_url)
|
||||
normalized = raw_open_id_url
|
||||
normalized = "http://#{raw_open_id_url}" unless raw_open_id_url =~ /\:\/\//
|
||||
normalized.downcase.chomp('/')
|
||||
end
|
||||
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue