knjtasks 0.0.3

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.
Files changed (69) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +18 -0
  4. data/Gemfile.lock +66 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +19 -0
  7. data/Rakefile +49 -0
  8. data/VERSION +1 -0
  9. data/files/database_schema.rb +174 -0
  10. data/lib/knjtasks.rb +172 -0
  11. data/locales/da_DK/LC_MESSAGES/default.mo +0 -0
  12. data/locales/da_DK/LC_MESSAGES/default.po +1153 -0
  13. data/locales/da_DK/title.txt +1 -0
  14. data/locales/en_GB/title.txt +1 -0
  15. data/models/class_comment.rb +44 -0
  16. data/models/class_customer.rb +13 -0
  17. data/models/class_email_check.rb +7 -0
  18. data/models/class_project.rb +22 -0
  19. data/models/class_task.rb +163 -0
  20. data/models/class_task_assigned_user.rb +62 -0
  21. data/models/class_task_check.rb +26 -0
  22. data/models/class_timelog.rb +82 -0
  23. data/models/class_user.rb +125 -0
  24. data/models/class_user_project_link.rb +66 -0
  25. data/models/class_user_rank.rb +3 -0
  26. data/models/class_user_rank_link.rb +17 -0
  27. data/models/class_user_task_list_link.rb +17 -0
  28. data/pages/admin.rhtml +7 -0
  29. data/pages/comment_edit.rhtml +121 -0
  30. data/pages/comment_update_id_per_obj.rhtml +41 -0
  31. data/pages/customer_edit.rhtml +69 -0
  32. data/pages/customer_search.rhtml +80 -0
  33. data/pages/customer_show.rhtml +50 -0
  34. data/pages/frontpage.rhtml +198 -0
  35. data/pages/project_edit.rhtml +129 -0
  36. data/pages/project_search.rhtml +82 -0
  37. data/pages/project_show.rhtml +203 -0
  38. data/pages/task_check_edit.rhtml +98 -0
  39. data/pages/task_edit.rhtml +168 -0
  40. data/pages/task_search.rhtml +131 -0
  41. data/pages/task_show.rhtml +454 -0
  42. data/pages/timelog_edit.rhtml +134 -0
  43. data/pages/timelog_search.rhtml +318 -0
  44. data/pages/user_edit.rhtml +223 -0
  45. data/pages/user_login.rhtml +83 -0
  46. data/pages/user_profile.rhtml +89 -0
  47. data/pages/user_rank_search.rhtml +95 -0
  48. data/pages/user_search.rhtml +136 -0
  49. data/pages/user_show.rhtml +87 -0
  50. data/pages/workstatus.rhtml +320 -0
  51. data/scripts/fckeditor_validate_login.rb +23 -0
  52. data/spec/knjtasks_spec.rb +115 -0
  53. data/spec/spec_helper.rb +12 -0
  54. data/threads/thread_mail_task_comments.rb +114 -0
  55. data/www/api/task.rhtml +9 -0
  56. data/www/api/user.rhtml +20 -0
  57. data/www/clean.rhtml +14 -0
  58. data/www/css/default.css +186 -0
  59. data/www/gfx/body_bg.jpg +0 -0
  60. data/www/gfx/button_bg.png +0 -0
  61. data/www/gfx/main_box_design.png +0 -0
  62. data/www/gfx/main_box_left.png +0 -0
  63. data/www/gfx/main_box_right.png +0 -0
  64. data/www/gfx/main_box_top.png +0 -0
  65. data/www/gfx/main_box_top_left.png +0 -0
  66. data/www/gfx/main_box_top_right.png +0 -0
  67. data/www/index.rhtml +154 -0
  68. data/www/js/default.js +112 -0
  69. metadata +208 -0
@@ -0,0 +1,23 @@
1
+ args = {
2
+ "host" => $validate_login["_SERVER"]["HTTP_HOST"]
3
+ }
4
+
5
+ if $validate_login["_SERVER"]["HTTPS"] or $validate_login["_SERVER"]["HTTP_SSL_ENABLED"]
6
+ args["ssl"] = true
7
+ args["port"] = 443
8
+ args["validate"] = false
9
+ end
10
+
11
+ http = Knj::Http.new(args)
12
+ http.cookies["KnjappserverSession"] = $validate_login["_COOKIE"]["KnjappserverSession"]
13
+ data = http.get("/?show=users_login")
14
+
15
+ if data["data"].to_s.index("<form method=\"post\" action=\"?show=user_login") == nil
16
+ print JSON.generate(
17
+ "Enabled" => true
18
+ )
19
+ else
20
+ print JSON.generate(
21
+ "Enabled" => false
22
+ )
23
+ end
@@ -0,0 +1,115 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Knjtasks" do
4
+ it "should be able to require the needed frameworks and gems" do
5
+ require "rubygems"
6
+ require "sqlite3"
7
+ require "knjrbfw"
8
+ require "knjtasks"
9
+ Knj.gem_require(:Hayabusa)
10
+ Knj.gem_require(:Http2)
11
+ end
12
+
13
+ it "should be able to require all the model-framework-files." do
14
+ models_path = "#{File.dirname(__FILE__)}/../models"
15
+ Dir.new(models_path).each do |file|
16
+ next if file == "." or file == ".."
17
+ fp = "#{models_path}/#{file}"
18
+ require fp
19
+ end
20
+ end
21
+
22
+ it "should be able to generate a sample database and start a test-environment." do
23
+ db_path = "#{Knj::Os.tmpdir}/knjtasks_spec_database_sample.sqlite3"
24
+ File.unlink(db_path) if File.exists?(db_path)
25
+
26
+ db = Knj::Db.new(
27
+ :type => "sqlite3",
28
+ :path => db_path,
29
+ :return_keys => "symbols",
30
+ :index_append_table_name => true
31
+ )
32
+ tasks = Knjtasks.new(
33
+ :title => "rake_rspec",
34
+ :host => "0.0.0.0",
35
+ :port => 1515,
36
+ :db => db,
37
+ :knjjs_url => "http://www.kaspernj.org/js",
38
+ :email_admin => "k@spernj.org",
39
+ :email_robot => "k@spernj.org",
40
+ :smtp_args => {
41
+ "smtp_host" => "localhost",
42
+ "smtp_port" => 25
43
+ },
44
+ :db_args => false
45
+ )
46
+
47
+ tasks.start
48
+ $tasks = tasks
49
+
50
+ #The admin-user should exists.
51
+ user = tasks.ob.get_by(:User, "username" => "admin", "passwd" => Digest::MD5.hexdigest("admin"))
52
+ raise "Expected admin user to exist but it didnt." if !user
53
+
54
+ $http = Http2.new(:host => "localhost", :port => 1515, :follow_redirects => false)
55
+ end
56
+
57
+ it "should be able to log in" do
58
+ res = $http.post(:url => "?show=user_login&choice=dologin", :post => {
59
+ "texuser" => "admin",
60
+ "texpass_md5" => Digest::MD5.hexdigest("admin"),
61
+ "cheremember" => "on"
62
+ })
63
+
64
+ res = $http.get("?show=user_login")
65
+ raise "Expected to be logged in but wasnt according to HTML:\n#{res.body}" if res.body.index("You are logged in as") == nil
66
+ end
67
+
68
+ it "should be able to add customers" do
69
+ res = $http.post(:url => "?show=customer_edit&choice=dosave", :post => {
70
+ "texname" => "Test customer"
71
+ })
72
+
73
+ if match = res.body.match(/location\.href="\?show=customer_edit&customer_id=(\d+)"/)
74
+ $customer = $tasks.ob.get(:Customer, match[1])
75
+ else
76
+ raise "Expected to be redirected to the new customer but wasnt:\n#{res.body}"
77
+ end
78
+ end
79
+
80
+ it "should be able to add projects" do
81
+ res = $http.post(:url => "?show=project_edit&choice=dosave", :post => {
82
+ "texname" => "Test project",
83
+ "selcustomer" => $customer.id,
84
+ "texdescr" => "Test project"
85
+ })
86
+
87
+ if match = res.body.match(/location\.href="\?show=project_edit&project_id=(\d+)"/)
88
+ $project = $tasks.ob.get(:Project, match[1])
89
+ else
90
+ raise "Expected to be redirected to new project but wasnt:\n#{res.body}"
91
+ end
92
+ end
93
+
94
+ it "should be able to add tasks" do
95
+ res = $http.post(:url => "?show=task_edit&choice=dosave", :post => {
96
+ "texname" => "Test task",
97
+ "texdescr" => "Test description",
98
+ "selproject" => $project.id,
99
+ "type" => "feature"
100
+ })
101
+
102
+ if match = res.body.match(/location\.href="\?show=task_show&task_id=(\d+)"/)
103
+ $task = $tasks.ob.get(:Task, match[1])
104
+ else
105
+ raise "Expected to be redirected to new task but wasnt:\n#{res.body}"
106
+ end
107
+ end
108
+
109
+ it "should show special HTML for expired sessions" do
110
+ res = $http.get(:url => "?show=user_login&choice=dologout")
111
+ res = $http.get(:url => "clean.rhtml?show=task_show&task_id=#{$task.id}&choice=gettimelogs")
112
+
113
+ raise "Expected message about not logged in but got this instead: '#{res.body}'." if res.body != "You are not logged in. Log in and try to view this page again."
114
+ end
115
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'knjtasks'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,114 @@
1
+ #This class checks the mail-account for mails and imports them as comments.
2
+ class Knjtasks::Thread_mail_task_comments
3
+ def initialize(args = {})
4
+ @args = args
5
+
6
+ require "rubygems"
7
+ require "mail"
8
+
9
+ if !@args[:args][:mail_args][:port]
10
+ @args[:args][:mail_args][:port] = 143 if @args[:args][:mail_args][:type] == "imap"
11
+ @args[:args][:mail_args][:port] = 100 if @args[:args][:mail_args][:type] == "pop3"
12
+ end
13
+ end
14
+
15
+ def run
16
+ STDOUT.print "Checking for mails.\n" if @args[:appsrv].debug
17
+ conn = Net::IMAP.new(@args[:args][:mail_args][:host], @args[:args][:mail_args][:port].to_i, @args[:args][:mail_args][:ssl])
18
+
19
+ if @args[:args][:mail_args][:user] and @args[:args][:mail_args][:pass]
20
+ conn.login(@args[:args][:mail_args][:user], @args[:args][:mail_args][:pass])
21
+ end
22
+
23
+ conn.select("INBOX")
24
+ emails = conn.search(["ALL"])
25
+ emails.each do |msg_id|
26
+ error = nil
27
+ from = nil
28
+ html = nil
29
+
30
+ begin
31
+ msg = conn.fetch(msg_id, "(ENVELOPE RFC822)")
32
+ mail = Mail.new(msg[0].attr["RFC822"])
33
+
34
+ env = msg[0].attr["ENVELOPE"]
35
+ from = "#{env.from.first.mailbox}@#{env.from.first.host}"
36
+ env_msg_id = env.message_id
37
+
38
+ if _ob.static(:Email_check, :checked_id?, env_msg_id)
39
+ next
40
+ else
41
+ _db.insert(:Email_check, {:email_id_str => env_msg_id})
42
+ end
43
+
44
+ user = @args[:ob].get_by(:User, {"email" => from})
45
+ raise "Could not find a user in this task-system with that email: '#{from}'." if !user
46
+
47
+ subj_match = mail.subject.to_s.match(/^([A-z]{1,3}):\s+(\S+)\s+#(\d+):\s+/)
48
+ raise "Could not figure out the task-ID from the email." if !subj_match
49
+ task_id = subj_match[3]
50
+
51
+ begin
52
+ task = @args[:ob].get(:Task, task_id)
53
+ rescue
54
+ raise "Could not find a task in this task-system with that task-ID: '#{task_id}'."
55
+ end
56
+
57
+ parts = {}
58
+ mail.parts.each do |part|
59
+ if part.content_type.match(/^text\/plain/)
60
+ parts[:plain] = part.body.to_s
61
+ elsif part.content_type.match(/^text\/html/) and match = part.body.match(/<body.*>([\s\S]+)<\/body>/)
62
+ parts[:html] = match[1]
63
+ end
64
+ end
65
+
66
+ if parts[:html]
67
+ html = parts[:html]
68
+ html.gsub!(/<blockquote([\s\S]+?)>([\s\S]+?)<\/blockquote>/i, "")
69
+ html.gsub!(/on\s+\d+.+?\s+wrote:/i, "")
70
+ html.gsub!(/<pre.+?class="moz-signature".*?>([\s\S]*?)<\/pre>/i, "")
71
+ html = Knj::Strings.strip(html, {
72
+ :strips => [" ", "\n", "\r", "<br>", "<br />", "<br/>"]
73
+ })
74
+ elsif parts[:plain]
75
+ html = parts[:plain]
76
+ html.gsub!(/on\s+\d+.+?\s+wrote:/i, "")
77
+ html.gsub!(/^>(.*?)\n/, "")
78
+ html.gsub!(/\n-- \n[\s\S]++/, "")
79
+ html = Knj::Strings.strip(html, {
80
+ :strips => ["\n", "\r", "<br>", "<br />", "<br/>"]
81
+ })
82
+ html.gsub!(/\n/, "<br>")
83
+ end
84
+
85
+ raise "Could not read any content in the email." if !html
86
+
87
+ comment = @args[:ob].add(:Comment, {
88
+ :object_class => :Task,
89
+ :object_id => task.id,
90
+ :comment => html,
91
+ :user_id => user.id
92
+ })
93
+
94
+ conn.store(msg_id, "+FLAGS", ["DELETED"])
95
+ conn.expunge
96
+ rescue => e
97
+ if !from
98
+ STDOUT.print "Could not respond to task-email."
99
+ STDOUT.puts e.inspect
100
+ STDOUT.puts e.backtrace
101
+ else
102
+ @args[:appsrv].mail(
103
+ :to => from,
104
+ :subject => "Your email to the task-system.",
105
+ :text => "Could not parse your email:\n\n#{e.inspect}\n#{e.backtrace.join("\n")}"
106
+ )
107
+ end
108
+ end
109
+ end
110
+
111
+ conn.logout
112
+ conn.disconnect
113
+ end
114
+ end
@@ -0,0 +1,9 @@
1
+ <%
2
+ arr = []
3
+
4
+ _ob.list(:Task) do |task|
5
+ arr << task.data
6
+ end
7
+
8
+ print JSON.generate(arr)
9
+ %>
@@ -0,0 +1,20 @@
1
+ <%
2
+ if _get["choice"] == "dologin"
3
+ user = _ob.get_by(:User, {
4
+ "username" => _post["username"],
5
+ "passwd" => _post["password"]
6
+ })
7
+ if !user
8
+ print JSON.generate(
9
+ "result" => false,
10
+ "msg" => "User not found."
11
+ )
12
+ else
13
+ print JSON.generate(
14
+ "result" => true,
15
+ "msg" => "Logged in as user #{user.id}.",
16
+ "user_data" => user.data
17
+ )
18
+ end
19
+ end
20
+ %>
@@ -0,0 +1,14 @@
1
+ <%
2
+ _site.load_request
3
+
4
+ show = _get["show"]
5
+ show = "frontpage" if !show
6
+ showp = "../pages/#{show}.rhtml"
7
+
8
+ if !_site.user
9
+ print _("You are not logged in. Log in and try to view this page again.")
10
+ exit
11
+ end
12
+
13
+ _hb.import(showp)
14
+ %>
@@ -0,0 +1,186 @@
1
+ html, body, input, textarea, select, td{
2
+ font-family: verdana, tahoma, arial;
3
+ font-size: 12px;
4
+ color: black;
5
+ }
6
+
7
+ html, body, html > body > center{
8
+ width: 100%;
9
+ height: 100%;
10
+ margin: 0px;
11
+ padding: 0px;
12
+ }
13
+
14
+ body{
15
+ background: url("/gfx/body_bg.jpg") 0 0 repeat;
16
+ }
17
+
18
+ a:link, a:visited{
19
+ color: #698295;
20
+ text-decoration: none;
21
+ }
22
+
23
+ a:active, a:hover{
24
+ color: #272727;
25
+ text-decoration: none;
26
+ }
27
+
28
+ table.main_table{
29
+ height: 100%;
30
+ width: 1200px;
31
+ border-top: none;
32
+ border-bottom: none;
33
+ text-align: left;
34
+ }
35
+
36
+ td.main_logo{
37
+ font-size: 24px;
38
+ padding: 15px;
39
+ padding-left: 28px;
40
+ height: 40px;
41
+ }
42
+
43
+ div.main_menu_outer{
44
+ position: relative;
45
+ height: 19px;
46
+ }
47
+
48
+ div.main_menu_inner{
49
+ position: absolute;
50
+ top: -15px;
51
+ font-size: 16px;
52
+ }
53
+
54
+ td.main_bottom{
55
+ text-align: center;
56
+ padding: 4px;
57
+ background-color: #ffffff;
58
+ height: 25px;
59
+ }
60
+
61
+ td.main_corner_top_left{
62
+ width: 41px;
63
+ height: 41px;
64
+ background: url("/gfx/main_box_top_left.png") 0 0 no-repeat;
65
+ }
66
+
67
+ td.main_corner_top_right{
68
+ width: 41px;
69
+ height: 41px;
70
+ background: url("/gfx/main_box_top_right.png") 0 0 no-repeat;
71
+ }
72
+
73
+ td.main_side_top{
74
+ height: 41px;
75
+ background: url("/gfx/main_box_top.png") 0 0 repeat-x;
76
+ }
77
+
78
+ td.main_side_left{
79
+ width: 41px;
80
+ background: url("/gfx/main_box_left.png") 0 0 repeat-y;
81
+ }
82
+
83
+ td.main_side_right{
84
+ width: 41px;
85
+ background: url("/gfx/main_box_right.png") 0 0 repeat-y;
86
+ }
87
+
88
+ td.main_body{
89
+ background-color: #ffffff;
90
+ vertical-align: top;
91
+ }
92
+
93
+ table.box{
94
+ border: none;
95
+ }
96
+
97
+ td.box_header{
98
+ font-weight: bold;
99
+ white-space: nowrap;
100
+ text-align: right;
101
+ background-color: #666666;
102
+ padding: 5px;
103
+ color: #d9d9d9;
104
+ }
105
+
106
+ td.box_header_spacer{
107
+ width: 27%;
108
+ }
109
+
110
+ td.box_content{
111
+ border: 1px solid #666666;
112
+ padding: 7px;
113
+ padding-right: 14px;
114
+ background-color: #ffffff;
115
+ }
116
+
117
+ .buttons{
118
+ text-align: right;
119
+ }
120
+
121
+ table.form, table.list{
122
+ width: 100%;
123
+ }
124
+
125
+ thead > tr > th{
126
+ font-size: 12px;
127
+ text-align: left;
128
+ white-space: nowrap;
129
+ }
130
+
131
+ td.tdt, td.tdcheck{
132
+ font-weight: bold;
133
+ white-space: nowrap;
134
+ }
135
+
136
+ td.tdc{
137
+ width: 100%;
138
+ }
139
+
140
+ td.tdd, div.tdd{
141
+ color: grey;
142
+ padding-top: 0px;
143
+ padding-bottom: 7px;
144
+ font-size: 10px;
145
+ }
146
+
147
+ td.tdr, th.tdr{
148
+ text-align: right;
149
+ }
150
+
151
+ .error{
152
+ font-style: italic;
153
+ }
154
+
155
+ td.error{
156
+ text-align: center;
157
+ }
158
+
159
+ input, select, textarea{
160
+ border: 1px solid #bbbbbb;
161
+ background-color: #ffffff;
162
+ color: #000000;
163
+ padding: 4px;
164
+ }
165
+
166
+ input[type=button], input[type=submit], input.button{
167
+ border: 1px solid #989898;
168
+ background: url("/gfx/button_bg.png") 0 0 repeat-x;
169
+ height: 20px;
170
+ font-size: 12px;
171
+ padding: 0px 9px 0px 9px;
172
+ margin: 0px 0px 0px 0px;
173
+ }
174
+
175
+ input[type=checkbox]{
176
+ border: none;
177
+ background-color: transparent;
178
+ }
179
+
180
+ input.input_text, input.input_password, select.input_select, textarea.input_textarea{
181
+ width: 100%;
182
+ }
183
+
184
+ .nowrap{
185
+ white-space: nowrap;
186
+ }