Browse Source

Activity log

It's a little unstable right now.
Nathaniel van Diepen 10 years ago
parent
commit
55c698fb5a

+ 8 - 7
api.php

@@ -91,7 +91,7 @@
 						'type'=>'pages',
 						'name'=>'project'
 					);
-					$ret['topbar'] = 'back';
+					$ret['topbar'] = 'project';
 					if($context = projectObj($id)){
 						$context['user'] = userObj($context['user']);
 						if($LOGGEDIN){
@@ -185,10 +185,10 @@
 											}
 										break;
 										case 'latest':
-											if($res = query("SELECT i.id,i.title,i.description,u.name as user,s.name as status,p.name as priority,p.color FROM `issues` i JOIN `users` u ON u.id = i.u_id LEFT JOIN `statuses` s ON s.id = i.st_id LEFT JOIN `priorities` p ON p.id = i.pr_id LIMIT 10")){
-												$context['issues'] = fetch_all($res,MYSQLI_ASSOC);
-												foreach($context['issues'] as $key => $issue){
-													$context['issues'][$key]['user'] = userObj($issue['user']);
+											if($res = query("SELECT a.date, a.id FROM `activity` AS a ORDER BY a.date DESC LIMIT 10")){
+												$context['activity'] = fetch_all($res,MYSQLI_ASSOC);
+												foreach($context['activity'] as $key => $activity){
+													$context['activity'][$key] = activityObj($activity['id']);
 												}
 											}
 										break;
@@ -277,6 +277,7 @@
 									$key = login($_GET['username'],$_GET['password']);
 									if($key){
 										$_SESSION['username'] = $_GET['username'];
+										$ret['key'] = $key;
 									}else{
 										$ret['error'] = "Login failed. Username or Password didn't match.";
 									}
@@ -300,7 +301,7 @@
 												$_SESSION['username'] = $_GET['username'];
 												sendMail('welcome','Welcome!',$_GET['email'],get('email'),array($_GET['username'],$_GET['password'],get('email')));
 											}else{
-												$ret['error'] = "Could not add user. ".$mysqli->error;
+												$ret['error'] = "Could not add user. ".get_sql()->error;
 											}
 										}else{
 											$ret['error'] = "Captcha did not match.";
@@ -344,7 +345,7 @@
 									$ret['error'] = 'Invalid Action';
 								}elseif(is_valid('title')&&is_valid('description')){
 									if(!newIssue($_GET['title'],$_GET['description'])){
-										$ret['error'] = 'Unable to create issue. ';
+										$ret['error'] = 'Unable to create issue. '.get_sql()->error;
 									}
 								}else{
 									$ret['error'] = 'Fill in all the details.';

+ 7 - 5
data/pages/index.template

@@ -40,17 +40,19 @@
 				Latest Issues
 			</header>
 			<ul>
-				{{#each issues}}
+				{{#each activity}}
 					<li>
 						<aside class="pack-end">
-							{{user.name}}
+							<time style="font-size:smaller;" class="timeago">
+								{{timestamp}}
+							</time>
 						</aside>
-						<a href="#!{{id}}">
+						<a href="{{url}}">
 							<p>
-								{{#if priority}}[<span style="{{#if color}}color:{{color}};{{/if}}">{{priority}}</span>] {{/if}}{{title}}{{#if status}} ({{status}}){{/if}}
+								{{rel.title}}
 							</p>
 							<p>
-								{{description}}
+								{{action}}
 							</p>
 						</a>
 					</li>

+ 10 - 2
data/pages/login.template

@@ -4,7 +4,7 @@
 	{{/if}}
 </script>
 <form id="form" style="display:none;">
-	<input name="username" class="fill-width" placeholder="Username" id="login-username" type="text"/>
+	<input name="username"  autocomplete="off" class="fill-width" placeholder="Username" id="login-username" type="text"/>
 	<input id="login-password" class="fill-width" placeholder="Password" name="password" type="password"/>
 	<button value="Cancel" class="danger">Cancel</button>
 	<button value="Login" onclick="$(this).parent().submit();">Login</button>
@@ -21,13 +21,16 @@
 				data.type = 'action';
 				data.id = 'login';
 				apiCall(data,function(d){
-					if(!d.error){
+					if(!exists(d.error) && exists(d.key)){
 						setKey(d.key);
 						notify('login','You have logged in successfully');
 						back(true);
 					}else{
 						setKey(null);
 						$('#loading').hide();
+						if(!exists(d.error)){
+							alert('Error communicating with the server');
+						}
 					}
 					return false;
 				});
@@ -35,6 +38,11 @@
 			}).find('.danger').click(function(){
 				back(true);
 				return false;
+			}).parent().find('input').keypress(function(e){
+				if(e.which == 13){
+					$('form#form').submit();
+					return false;
+				}
 			});
 		});
 	{{/unless}}

+ 24 - 3
data/pages/project.template

@@ -39,6 +39,27 @@
 <h3>
 	Scrums
 </h3>
-{{#each scrums}}
-	<a class="button" href="#scrum-{{id}}">{{title}}</a>
-{{/each}}
+<div>
+	<section data-type="list">
+		<header>
+			Scrums
+		</header>
+		<ul>
+			{{#each scrums}}
+				<li>
+					<aside class="pack-end">
+						{{user.name}}
+					</aside>
+					<a href="#scrum-{{id}}">
+						<p>
+							{{title}}
+						</p>
+						<p>
+							{{description}}
+						</p>
+					</a>
+				</li>
+			{{/each}}
+		</ul>
+	</section>
+</div>

+ 62 - 42
data/sidebars/default.template

@@ -12,46 +12,66 @@
 	<h2>
 		Pages
 	</h2>
-	<ul>
-		<li>
-			<a href="#page-index">
-				Home
-			</a>
-		</li>
-		<li>
-			<a href="#page-projects">
-				Projects
-			</a>
-		</li>
-		<li>
-			<a href="#page-issues">
-				Issues
-			</a>
-		</li>
-		<li>
-			<a href="#page-users">
-				Users
-			</a>
-		</li>
-	</ul>
-	<h2>
-		{{user.name}}
-	</h2>
-	<ul>
-		<li>
-			<a href="#page-profile">
-				Profile
-			</a>
-		</li>
-		<li>
-			<a href="#page-messages">
-				Messages
-			</a>
-		</li>
-		<li>
-			<a href="#page-logout">
-				Logout
-			</a>
-		</li>
-	</ul>
+	{{#if user}}
+		<ul>
+			<li>
+				<a href="#page-index">
+					Home
+				</a>
+			</li>
+			<li>
+				<a href="#page-projects">
+					Projects
+				</a>
+			</li>
+			<li>
+				<a href="#page-issues">
+					Issues
+				</a>
+			</li>
+			<li>
+				<a href="#page-users">
+					Users
+				</a>
+			</li>
+		</ul>
+		<h2>
+			{{user.name}}
+		</h2>
+		<ul>
+			<li>
+				<a href="#page-profile">
+					Profile
+				</a>
+			</li>
+			<li>
+				<a href="#page-messages">
+					Messages
+				</a>
+			</li>
+			<li>
+				<a href="#page-logout">
+					Logout
+				</a>
+			</li>
+		</ul>
+	{{else}}
+		<ul>
+			<li>
+				<a href="#page-index">
+					Home
+				</a>
+			</li>
+			<li>
+				<a href="#page-login">
+					Login
+				</a>
+			</li>
+			<li>
+				<a href="#page-register">
+					Register
+				</a>
+			</li>
+		</ul>
+	{{/if}}
 </nav>

+ 14 - 0
data/topbars/project.options

@@ -0,0 +1,14 @@
+{
+	"actions":{
+		"new": [
+			{
+				"name": "New Comment",
+				"action": "page-newcommentproject"
+			},
+			{
+				"name": "New Scrum",
+				"action": "page-newscrum"
+			}
+		]
+	}
+}

+ 20 - 0
data/topbars/project.template

@@ -0,0 +1,20 @@
+<a id="btn-lists-back">
+	<span class="icon icon-back">
+		back
+	</span>
+</a>
+<menu type="toolbar">
+	<a>
+		<span class="icon icon-edit">
+			edit
+		</span>
+	</a>
+	<a>
+		<span class="icon icon-add">
+			add
+		</span>
+	</a>
+</menu>
+<h1>
+	{{title}}
+</h1>

+ 9 - 1
index.php

@@ -35,7 +35,7 @@
 			break;
 			case 'settings':
 				$settings = array();
-				$keys = array();
+				$keys = array('expire');
 				foreach($keys as $key){
 					$settings[$key] = get($key);
 				}
@@ -157,6 +157,14 @@
 				<article id="content" class="scrollable header"></article>
 			</section>
 		</section>
+		<!-- <section id="action-menu" data-position="back" class="fade-in">
+			<form role="dialog" data-type="action">
+				<header>
+					Title
+				</header>
+				<menu></menu>
+			</form>
+		</section> -->
 		<div id="loading"></div>
 		<div id="dialog"></div>
 		<div id="comment" style="display:none;">

+ 1 - 2
install/api.php

@@ -115,7 +115,6 @@
 					if($id == "run"){
 						$path = realpath(dirname(__FILE__));
 						if(isset($_GET['dbuser'])&&isset($_GET['dbpass'])&&isset($_GET['dbname'])&&isset($_GET['dbhost'])&&isset($_GET['dbtemplate'])&&isset($_GET['email'])){
-							global $mysqli;
 							$dbuser = $_GET['dbuser'];
 							$dbpass = $_GET['dbpass'];
 							$dbname = $_GET['dbname'];
@@ -129,7 +128,7 @@
 							file_put_contents('../config.default.json',"{\"host\":\"{$dbhost}\",\"user\":\"{$dbuser}\",\"password\":\"{$dbpass}\",\"database\":\"{$dbname}\",\"expire\":86400,\"email\":\"{$email}\"}");
 							require_once('../php/database.php');
 							foreach($sql_query as $sql){
-								query($sql) or die('Error in query: '.$mysqli->error);
+								query($sql) or die('Error in query: '.get_sql()->error);
 							}
 							echo 'pass';
 						}else{

+ 18 - 9
js/index.js

@@ -1,5 +1,6 @@
    // TODO - Add initial page loading and handlers
 (function($,History,console){
+	//"use strict";
 	var State = History.getState(),
 		Old = {},
 		Key = null,
@@ -9,16 +10,17 @@
 		flag = window.flag = function(name,value){
 			if(exists(value)){
 				flags[name] = value;
+				return value;
 			}else{
 				return exists(flags[name])?flags[name]:false;
 			}
 		},
 		settings = {},
-		exists = function(v){
+		exists = window.exists = function(v){
 			return typeof v != 'undefined';
 		},
 		get = window.get = function(s){
-			return settings[s];
+			return exists(settings[s])?settings[s]:null;
 		},
 		set = window.set = function(s,v){
 			settings[s] = v;
@@ -31,7 +33,8 @@
 				var d = new Date();
 				d.setTime(d.getTime()+get('expire'));
 				$.cookie('key',key,{
-					expires: d
+					expires: d,
+					path: '/'
 				});
 			}else{
 				console.log('Key deleted');
@@ -52,8 +55,14 @@
 		},
 		action = window.action = function(name){
 			if(exists(actions[name])){
-				loadState(actions[name]);
+				if(actions[name] instanceof Array){
+
+				}else{
+					loadState(actions[name]);
+					return true;
+				}
 			}
+			return false;
 		},
 		template = window.template = function(type,name,template,hash){
 			var id = (function(type,name){
@@ -71,7 +80,6 @@
 					}
 					$.localStorage('templates',templates);
 					console.log('Dropping template for '+type+':'+name);
-					return '';
 				}else{
 					var o = {
 						name: name,
@@ -93,8 +101,8 @@
 				return templates[id].template;
 			}else{
 				console.log('No cached template stored for: '+type+':'+name);
-				return '';
 			}
+			return '';
 		},
 		apiCall = window.apiCall = function(data,callback,background){
 			console.log('apiCall('+data.type+'-'+data.id+')');
@@ -194,7 +202,7 @@
 						async: true,
 						type: 'GET',
 						success: function(d){
-							if(exists(d['error'])){
+							if(exists(d.error)){
 								error(d);
 							}else{
 								d.state.title = d.state.title.capitalize();
@@ -275,6 +283,7 @@
 									}
 									$(window).resize();
 									loading(false);
+
 								}else if(d.state.url != location.href){
 									console.error('No context given');
 									console.log(d);
@@ -625,7 +634,7 @@
 		},
 		notify = window.notify = function(title,text,onclick,onclose){
 			var notification;
-			if(exists(window['Notification'])&&!exists(window.webkitNotifications)&&!flag('default_notify')&&!hasFocus()){
+			if(exists(window['Notification'])&&!exists(window.webkitNotifications)&&!flag('default_notify')){
 				if(Notification.permission === 'denied'){
 					flag('default_notify',true);
 					notify(title,text,onclick,onclose);
@@ -643,7 +652,7 @@
 						notify(title,text,onclick,onclose);
 					});
 				}
-			}else if(exists(window.navigator.mozNotification)&&!hasFocus()){
+			}else if(exists(window.navigator.mozNotification)){
 				notification = window.navigator.mozNotification.createNotification(title,text,location.origin+'/img/favicon.ico');
 				notification.onclick = onclick;
 				notification.onclose = onclose;

+ 82 - 0
php/activity.php

@@ -0,0 +1,82 @@
+<?php
+	require_once(realpath(dirname(__FILE__)).'/config.php');
+	require_once(PATH_PHP.'database.php');
+	function alog($type,$id,$action){
+		$aid = create_action($action);
+		if(query("INSERT INTO `activity` (%s_id,a_id) VALUES (%d,%d)",array($type,$id,$aid))){
+			return true;
+		}
+		return false;
+	}
+	function create_action($action){
+		if($aid = get_action_id($action)){
+			return $aid;
+		}elseif(query("INSERT INTO `actions` (name) VALUES ('%s')",array($action))){
+			return mysqli_insert_id(get_sql());
+		}
+		return false;
+	}
+	function get_action_id($action){
+		if($res = query("SELECT a.id,a.name FROM `actions` AS a WHERE a.name = '%s'",array($action))){
+			if($res->num_rows == 1){
+				$action = $res->fetch_assoc();
+				return $action['id'];
+			}
+		}
+		return false;
+	}
+	function activityObj($id){
+		if($res = query("SELECT UNIX_TIMESTAMP(a.date) as timestamp,
+								a.id,
+								ac.name AS action,
+								CASE
+									WHEN a.u_id IS NOT NULL THEN 'u'
+									WHEN a.p_id IS NOT NULL THEN 'p'
+									WHEN a.i_id IS NOT NULL THEN 'i'
+									WHEN a.m_id IS NOT NULL THEN 'm'
+									ELSE 'other'
+								END AS type,
+								a.u_id,
+								a.p_id,
+								a.i_id,
+								a.m_id
+			FROM `activity` AS a
+			JOIN `actions` AS ac ON ac.id = a.a_id
+			WHERE a.id = %d
+		",array($id))){
+			if($res->num_rows == 1){
+				$activity = $res->fetch_assoc();
+				$ret = array(
+					'id'=>$activity['id'],
+					'timestamp'=>$activity['timestamp'],
+					'action'=>$activity['action'],
+					'type'=>$activity['type']
+				);
+				switch($activity['type']){
+					case 'u':
+						$ret['rel'] = userObj($activity['u_id']);
+						$ret['url'] = '#~'.$ret['rel']['name'];
+					break;
+					case 'p':
+						$ret['rel'] = projectObj($activity['p_id']);
+						$ret['url'] = '#project-'.$ret['rel']['id'];
+						$ret['rel']['title'] = 'Project - '.$ret['rel']['title'];
+					break;
+					case 'i':
+						$ret['rel'] = issueObj($activity['i_id']);
+						$ret['url'] = '#!'.$ret['rel']['id'];
+						$ret['rel']['title'] = 'Issue - '.$ret['rel']['title'];
+					break;
+					case 'm':
+						$ret['rel'] = messageObj($activity['i_id']);
+						$ret['url'] = '#message-'.$ret['rel']['id'];
+					break;
+					default:
+						$ret['rel'] = array();
+				}
+				return $ret;
+			}
+		}
+		return false;
+	}
+?>

+ 15 - 9
php/database.php

@@ -1,22 +1,28 @@
 <?php
 	require_once(realpath(dirname(__FILE__)).'/config.php');
-	$mysqli = new mysqli(get('host'),get('user'),get('password'),get('database'));
-	if($mysqli && $mysqli->connect_errno){
-		die("Failed to connect to MySQL: ".$mysqli->connect_error);
-	}
-	if(!$mysqli->autocommit(true)){
-		die("Failed to connect to MySQL: ".$mysqli->connect_error);
+	function get_sql(){
+		static $sql;
+		if(is_null($sql)){
+			$sql = new mysqli(get('host'),get('user'),get('password'),get('database'));
+			if($sql && $sql->connect_errno){
+				die("Failed to connect to MySQL: ".$sql->connect_error);
+			}
+			if(!$sql->autocommit(true)){
+				die("Failed to connect to MySQL: ".$sql->connect_error);
+			}
+		}
+		return $sql;
 	}
 	function query($query,$args=Array()){
-		global $mysqli;
+		$sql = get_sql();
 		for ($i=0;$i<count($args);$i++){
 			if(is_string($args[$i])){
-				$args[$i] = $mysqli->real_escape_string($args[$i]);
+				$args[$i] = get_sql()->real_escape_string($args[$i]);
 			}elseif(!is_numeric($args[$i])){
 				return false;
 			}
 		}
-		return $mysqli->query(vsprintf($query,$args));
+		return get_sql()->query(vsprintf($query,$args));
 	}
 	function fetch_all($result,$type=MYSQLI_NUM){
 		if(method_exists('mysqli_result', 'fetch_all')){

+ 1 - 1
php/functions.php

@@ -114,7 +114,7 @@
 		}
 		retj(array(
 			'state'=>array(
-				'url'=>isset($_GET['back'])?$_GET['back']:'page-index'
+				'url'=>'page-index'
 			)
 		));
 	}

+ 5 - 8
php/issue.php

@@ -2,6 +2,7 @@
 	require_once(realpath(dirname(__FILE__)).'/config.php');
 	require_once(PATH_PHP.'database.php');
 	require_once(PATH_PHP.'messages.php');
+	require_once(PATH_PHP.'activity.php');
 	function issueObj($id){
 		if($res = query("SELECT i.id,i.title,i.description,i.s_id,u.name as user, p.name as priority, s.name as status,p.color FROM `issues` i JOIN `users` u ON u.id = i.u_id LEFT JOIN `priorities` p ON p.id = i.pr_id LEFT JOIN `statuses` s ON s.id = i.st_id WHERE i.id='%d'",array($id))){
 			if($res->num_rows == 1){
@@ -15,7 +16,6 @@
 	}
 	function newIssue($title,$description,$user=null,$sid=null){
 		global $LOGGEDIN;
-		global $mysqli;
 		if($LOGGEDIN){
 			if(is_null($user)){
 				$user = $_SESSION['username'];
@@ -33,13 +33,10 @@
 					$sid = intval($sid);
 				}
 				if(query("INSERT INTO `issues` (title,description,u_id,s_id,st_id) VALUES ('%s','%s',%d,%s,1)",array($title,$description,$user,$sid))){
-					if($res = query("SELECT id FROM `issues` WHERE title = '%s' AND description = '%s' AND u_id = %d",array($title,$description,$user,$sid))){
-						if($res->num_rows == 1){
-							$res = $res->fetch_assoc();
-							project_comment($res['id'],'Issue created');
-						}
-						return true;
-					}
+					$id = mysqli_insert_id(get_sql());
+					project_comment($id,'Issue created');
+					alog('i',$id,"Issue created");
+					return true;
 				}
 			}
 		}

+ 7 - 0
php/messages.php

@@ -43,14 +43,21 @@
 		}
 		return array();
 	}
+	function messageObj($id){
+		$message = array();
+
+		return $message;
+	}
 	function project_comment($project,$message){
 		if(query("INSERT INTO `bugs`.`messages` (`id`,`timestamp`,`from_id`,`to_id`,`p_id`,`s_id`,`i_id`,`message`) VALUES(NULL,CURRENT_TIMESTAMP,'%d',NULL,'%d',NULL,NULL,'%s');",array(userId($_SESSION['username']),$project,$message))){
+			alog('p',$project,'Comment added');
 			return true;
 		}
 		return false;
 	}
 	function issue_comment($issue,$message){
 		if(query("INSERT INTO `bugs`.`messages` (`id`,`timestamp`,`from_id`,`to_id`,`p_id`,`s_id`,`i_id`,`message`) VALUES(NULL,CURRENT_TIMESTAMP,'%d',NULL,NULL,NULL,'%d','%s');",array(userId($_SESSION['username']),$issue,$message))){
+			alog('i',$issue,'Comment added');
 			return true;
 		}
 		return false;

+ 4 - 8
php/project.php

@@ -2,6 +2,7 @@
 	require_once(realpath(dirname(__FILE__)).'/config.php');
 	require_once(PATH_PHP.'database.php');
 	require_once(PATH_PHP.'messages.php');
+	require_once(PATH_PHP.'activity.php');
 	function projectObj($id){
 		if($res = query("SELECT p.title,p.id,p.description,u.name as user FROM `projects` p JOIN `users` u ON u.id = p.u_id  WHERE p.id='%d'",array($id))){
 			if($res->num_rows == 1){
@@ -16,7 +17,6 @@
 	}
 	function newProject($title,$description,$user=null){
 		global $LOGGEDIN;
-		global $mysqli;
 		if($LOGGEDIN){
 			if(is_null($user)){
 				$user = $_SESSION['username'];
@@ -29,13 +29,9 @@
 					}
 				}
 				if(query("INSERT INTO `projects` (title,description,u_id) VALUES ('%s','%s',%d)",array($title,$description,$user))){
-					if($res = query("SELECT id FROM `projects` WHERE title = '%s' AND description = '%s' AND u_id = %d",array($title,$description,$user))){
-						if($res->num_rows == 1){
-							$res = $res->fetch_assoc();
-							project_comment($res['id'],'Project created');
-						}
-						return true;
-					}
+					project_comment($id,'Project created');
+					alog('p',$id,"Project created");
+					return true;
 				}
 			}
 		}

+ 7 - 7
php/user.php

@@ -2,14 +2,14 @@
 	require_once(realpath(dirname(__FILE__)).'/config.php');
 	require_once(PATH_PHP.'database.php');
 	require_once(PATH_PHP.'security.php');
-	global $mysqli;
 	function addUser($username,$password,$email){
-		global $mysqli;
-		$salt = $mysqli->escape_string(salt());
-		$email = $mysqli->escape_string($email);
-		$username = $mysqli->escape_string($username);
-		$hash = $mysqli->escape_string(saltedHash($password,$salt));
-		return query("INSERT INTO `users` (email,name,password,salt) VALUES ('%s','%s','%s','%s')",Array($email,$username,$hash,$salt));
+		$salt = get_sql()->escape_string(salt());
+		$email = get_sql()->escape_string($email);
+		$username = get_sql()->escape_string($username);
+		$hash = get_sql()->escape_string(saltedHash($password,$salt));
+		$res = query("INSERT INTO `users` (email,name,password,salt) VALUES ('%s','%s','%s','%s')",Array($email,$username,$hash,$salt));
+		alog('u',mysqli_insert_id(get_sql()),'User Created');
+		return $res;
 	}
 	function isUser($name){
 		$res = query("SELECT id FROM `users` WHERE name='%s'",Array($name));