Browse Source

several developers per project.
added web interface.

Nicolas Cannasse 18 years ago
parent
commit
ceb6560d4b

+ 6 - 6
std/tools/haxelib/Datas.hx

@@ -29,11 +29,11 @@ typedef ProjectInfos = {
 typedef XmlInfos = {
 	var project : String;
 	var website : String;
-	var owner : String;
 	var desc : String;
 	var license : String;
 	var version : String;
 	var versionComments : String;
+	var developers : List<String>;
 	var dependencies : List<{ project : String, version : String }>;
 }
 
@@ -98,7 +98,7 @@ class Datas {
 			"project",
 			[ sname, Att("url"), Att("license",FEnum(LICENSES)) ],
 			RList([
-				RNode("user",[sname]),
+				RMulti( RNode("user",[sname]), true ),
 				RNode("description",[],RData()),
 				RNode("version",[sname],RData()),
 				RMulti(	RNode("depends",[sname,Att("version",FReg(alphanum),"")]) ),
@@ -111,20 +111,20 @@ class Datas {
 		var project = p.att.name;
 		if( project.length < 3 )
 			throw "Project name must contain at least 3 characters";
-		var user = p.node.user.att.name;
-		if( user.length < 3 )
-			throw "User name must contain at least 3 characters";
+		var devs = new List();
+		for( d in p.nodes.user )
+			devs.add(d.att.name);
 		var deps = new List();
 		for( d in p.nodes.depends )
 			deps.add({ project : d.att.name, version : d.att.version });
 		return {
 			project : project,
-			owner : user,
 			website : p.att.url,
 			desc : p.node.description.innerData,
 			version : p.node.version.att.name,
 			versionComments : p.node.version.innerData,
 			license : p.att.license,
+			developers : devs,
 			dependencies : deps,
 		}
 	}

+ 20 - 12
std/tools/haxelib/Main.hx

@@ -90,7 +90,7 @@ class ProgressIn extends neko.io.Input {
 
 class Main {
 
-	static var VERSION = 102;
+	static var VERSION = 103;
 	static var REPNAME = "lib";
 	static var SERVER = {
 		host : "lib.haxe.org",
@@ -116,6 +116,7 @@ class Main {
 		addCommand("search",search,"list projects matching a word");
 		addCommand("info",info,"list informations on a given project");
 		addCommand("user",user,"list informations on a given user");
+		addCommand("register",register,"register a new user");
 		addCommand("submit",submit,"submit or update a project package");
 		addCommand("setup",setup,"set the haxelib repository path");
 		addCommand("config",config,"print the repository path");
@@ -247,9 +248,12 @@ class Main {
 			print("  "+p);
 	}
 
-	function register(name) {
-		print("This is your first submission as '"+name+"'");
-		print("Please enter the following informations for registration");
+	function register() {
+		doRegister(param("User"));
+		print("Registration successful");
+	}
+
+	function doRegister(name) {
 		var email = param("Email");
 		var fullname = param("Fullname");
 		var pass = param("Password",true);
@@ -266,15 +270,20 @@ class Main {
 		var data = neko.io.File.getContent(file);
 		var zip = neko.zip.File.read(new neko.io.StringInput(data));
 		var infos = Datas.readInfos(zip);
+		var user = infos.developers.first();
 		var password;
-		site.checkOwner(infos.project,infos.owner);
-		if( site.isNewUser(infos.owner) )
-			password = register(infos.owner);
-		else {
+		if( site.isNewUser(user) ) {
+			print("This is your first submission as '"+user+"'");
+			print("Please enter the following informations for registration");
+			password = doRegister(user);
+		} else {
+			if( infos.developers.length > 1 )
+				user = param("User");
 			password = haxe.Md5.encode(param("Password",true));
-			if( !site.checkPassword(infos.owner,password) )
-				throw "Invalid password for "+infos.owner;
+			if( !site.checkPassword(user,password) )
+				throw "Invalid password for "+user;
 		}
+		site.checkDeveloper(infos.project,user);
 
 		// check dependencies validity
 		for( d in infos.dependencies ) {
@@ -291,7 +300,6 @@ class Main {
 				throw "Project "+d.project+" does not have version "+d.version;
 		}
 
-
 		// query a submit id that will identify the file
 		var id = site.getSubmitId();
 
@@ -304,7 +312,7 @@ class Main {
 		h.request(true);
 
 		// ask the server to register the sent file
-		var msg = site.processSubmit(id,password);
+		var msg = site.processSubmit(id,user,password);
 		print(msg);
 	}
 

+ 64 - 3
std/tools/haxelib/Site.hx

@@ -59,15 +59,76 @@ class Site {
 			neko.Lib.print("File #"+sid+" accepted : "+bytes+" bytes written");
 			return;
 		}
-		neko.Lib.print("I'm haXLib");
+		display();
+	}
+
+	static function display() {
+		var data = neko.io.File.getContent(CWD + "website.mtt");
+		var page = new haxe.Template(data);
+		var ctx : Dynamic = Reflect.empty();
+		var macros = {
+			download : function( res, p, v ) {
+				return "/"+Datas.REPOSITORY+"/"+Datas.fileName(res(p).name,res(v).name);
+			}
+		};
+		fillContent(ctx);
+		neko.Lib.print( page.execute(ctx,macros) );
+	}
+
+	static function fillContent( ctx : Dynamic ) {
+		var uri = neko.Web.getURI().split("/");
+		if( uri[0] == "" )
+			uri.shift();
+		var act = uri.shift();
+		if( act == null || act == "" )
+			act = "index";
+		ctx.projects = Project.manager.allByName();
+		switch( act ) {
+		case "p":
+			var name = uri.shift();
+			var p = Project.manager.search({ name : name }).first();
+			if( p == null ) {
+				ctx.error = "Unknown project '"+name+"'";
+				return;
+			}
+			ctx.p = p;
+			ctx.owner = p.owner;
+			ctx.version = p.version;
+			ctx.versions = Version.manager.byProject(p);
+		case "u":
+			var name = uri.shift();
+			var u = User.manager.search({ name : name }).first();
+			if( u == null ) {
+				ctx.error = "Unknown user '"+name+"'";
+				return;
+			}
+			ctx.u = u;
+			ctx.uprojects = Developer.manager.search({ user : u.id }).map(function(d:Developer) { return d.project; });
+		case "index":
+			var vl = Version.manager.latest(10);
+			for( v in vl ) {
+				var p = v.project;
+			}
+			ctx.versions = vl;
+		default:
+			ctx.error = "Unknown action : "+act;
+			return;
+		}
+		Reflect.setField(ctx,"act_"+act,true);
 	}
 
 	static function main() {
+		var error = null;
 		initDatabase();
-		run();
+		try {
+			run();
+		} catch( e : Dynamic ) {
+			error = { e : e };
+		}
 		db.close();
 		neko.db.Manager.cleanup();
-		return;
+		if( error != null )
+			neko.Lib.rethrow(error.e);
 	}
 
 }

+ 47 - 11
std/tools/haxelib/SiteApi.hx

@@ -52,6 +52,8 @@ class SiteApi {
 	public function register( name : String, pass : String, mail : String, fullname : String ) : Bool {
 		if( !Datas.alphanum.match(name) )
 			throw "Invalid user name, please use alphanumeric characters";
+		if( name.length < 3 )
+			throw "User name must be at least 3 characters";
 		var u = new User();
 		u.name = name;
 		u.pass = pass;
@@ -65,12 +67,14 @@ class SiteApi {
 		return User.manager.search({ name : name }).first() == null;
 	}
 
-	public function checkOwner( prj : String, user : String ) : Void {
+	public function checkDeveloper( prj : String, user : String ) : Void {
 		var p = Project.manager.search({ name : prj }).first();
 		if( p == null )
 			return;
-		if( p.owner.name != user )
-			throw "Owner of "+p.name+" is '"+p.owner.name+"'";
+		for( d in Developer.manager.search({ project : p.id }) )
+			if( d.user.name == user )
+				return;
+		throw "User '"+user+"' is not a developer of project '"+prj+"'";
 	}
 
 	public function checkPassword( user : String, pass : String ) : Bool {
@@ -82,7 +86,7 @@ class SiteApi {
 		return Std.string(Std.random(100000000));
 	}
 
-	public function processSubmit( id : String, pass : String ) : String {
+	public function processSubmit( id : String, user : String, pass : String ) : String {
 		var path = Site.TMP_DIR+"/"+Std.parseInt(id)+".tmp";
 
 		var file = try neko.io.File.read(path,true) catch( e : Dynamic ) throw "Invalid file id #"+id;
@@ -90,11 +94,20 @@ class SiteApi {
 		file.close();
 
 		var infos = Datas.readInfos(zip);
-		var u = User.manager.search({ name : infos.owner }).first();
+		var u = User.manager.search({ name : user }).first();
 		if( u == null || u.pass != pass )
 			throw "Invalid username or password";
 
+		var devs = infos.developers.map(function(user) {
+			var u = User.manager.search({ name : user }).first();
+			if( u == null )
+				throw "Unknown user '"+user+"'";
+			return u;
+		});
+
 		var p = Project.manager.search({ name : infos.project }).first();
+
+		// create project if needed
 		if( p == null ) {
 			p = new Project();
 			p.name = infos.project;
@@ -103,20 +116,43 @@ class SiteApi {
 			p.license = infos.license;
 			p.owner = u;
 			p.insert();
-			neko.FileSystem.deleteFile(path);
-			return "Project added : submit one more time to send a first version";
+			for( u in devs ) {
+				var d = new Developer();
+				d.user = u;
+				d.project = p;
+				d.insert();
+			}
 		}
 
-		// check owner
-		if( p.owner != u )
-			throw "Invalid owner";
+		// check submit rights
+		var pdevs = Developer.manager.search({ project : p.id });
+		var isdev = false;
+		for( d in pdevs )
+			if( d.user.id == u.id ) {
+				isdev = true;
+				break;
+			}
+		if( !isdev )
+			throw "You are not a developer of this project";
 
 		// update public infos
 		var update = false;
-		if( infos.desc != p.description || p.website != infos.website ) {
+		if( infos.desc != p.description || p.website != infos.website || pdevs.length != devs.length ) {
+			if( u.id != p.owner.id )
+				throw "Only project owner can modify project infos";
 			p.description = infos.desc;
 			p.website = infos.website;
 			p.update();
+			if( pdevs.length != devs.length ) {
+				for( d in pdevs )
+					d.delete();
+				for( u in devs ) {
+					var d = new Developer();
+					d.user = u;
+					d.project = p;
+					d.insert();
+				}
+			}
 			update = true;
 			neko.FileSystem.deleteFile(path);
 			return "Project infos updated : submit one more time to send a new version";

+ 41 - 1
std/tools/haxelib/SiteDb.hx

@@ -39,7 +39,7 @@ class Version extends neko.db.Object {
 		return [{ key : "project", prop : "project", manager : Project.manager }];
 	}
 
-	public static var manager = new neko.db.Manager<Version>(Version);
+	public static var manager = new VersionManager(Version);
 
 	public var id : Int;
 	public var project(dynamic,dynamic) : Project;
@@ -50,6 +50,23 @@ class Version extends neko.db.Object {
 
 }
 
+class Developer extends neko.db.Object {
+
+	static var TABLE_IDS = ["user","project"];
+	static function RELATIONS() {
+		return [
+			{ key : "user", prop : "user", manager : User.manager },
+			{ key : "project", prop : "project", manager : Project.manager },
+		];
+	}
+
+	public static var manager = new neko.db.Manager<Developer>(Developer);
+
+	public var user(dynamic,dynamic) : User;
+	public var project(dynamic,dynamic) : Project;
+
+}
+
 class ProjectManager extends neko.db.Manager<Project> {
 
 	public function containing( word ) {
@@ -57,6 +74,22 @@ class ProjectManager extends neko.db.Manager<Project> {
 		return results("SELECT name FROM Project WHERE name LIKE "+word+" OR description LIKE "+word);
 	}
 
+	public function allByName() {
+		return objects("SELECT * FROM Project ORDER BY name COLLATE NOCASE",false);
+	}
+
+}
+
+class VersionManager extends neko.db.Manager<Version> {
+
+	public function latest( n : Int ) {
+		return objects("SELECT * FROM Version ORDER BY date DESC LIMIT "+n,false);
+	}
+
+	public function byProject( p : Project ) {
+		return objects("SELECT * FROM Version WHERE project = "+p.id+" ORDER BY date DESC",false);
+	}
+
 }
 
 class SiteDb {
@@ -95,5 +128,12 @@ class SiteDb {
 				comments TEXT NOT NULL
 			)
 		");
+		db.request("DROP TABLE IF EXISTS Developer");
+		db.request("
+			CREATE TABLE Developer (
+				user INTEGER NOT NULL PRIMARY KEY,
+				project INTEGER NOT NULL PRIMARY KEY
+			)
+		");
 	}
 }

+ 240 - 0
std/tools/haxelib/website.mtt

@@ -0,0 +1,240 @@
+<html>
+
+<head>
+<title>lib.haxe.org</title>
+<style type="text/css">
+
+/* structure */
+
+body {
+	margin : 0;
+	padding : 0;
+	text-align: center;
+}
+
+.page {
+	margin: 10px auto 10px auto;
+	width : 750px;
+}
+
+.menu {
+	text-align : left;
+	float : left;
+	width : 150px;
+}
+
+.content {
+	text-align : left;
+	float : left;
+	width : 600px;
+}
+
+/* style */
+
+a {
+	color : #ff8400;
+	text-decoration : none;
+}
+
+a:hover {
+	text-decoration : underline;
+}
+
+a:visited {
+	color : #ff8400;
+}
+
+h1 a, h1 a:hover, h1 a:visited {
+	text-align : center;
+	color : black;
+	text-decoration : none;
+}
+
+.content p {
+	text-align : justify;
+	margin : 0px;
+	padding : 0px 10px 0px 10px;
+}
+
+.menu ul {
+	list-style : none;
+	margin : 5px;
+	padding : 0px;
+}
+
+.versions .date, .versions .project, .versions .name {
+	display : inline;
+}
+
+.versions ul, .projects ul {
+	list-style : circle;
+	margin : 25px;
+	padding : 0px;
+}
+
+.date {
+	color : #555;
+	font-size : 12px;
+}
+
+.versions .name {
+	font-weight : bold;
+}
+
+.versions .download {
+	float : right;
+	margin-top : -22px;
+	margin-right : 30px;
+}
+
+.download {
+	padding : 2px 4px 2px 4px;
+	background-color : #eee;
+	display : inline;
+}
+
+.download a {
+	color : #555;
+	font-size : 12px;
+	text-decoration : none;
+}
+
+.download a:visited {
+	color : #555;
+}
+
+.versions .comments {
+	margin-right : 40px;
+	margin-left : 10px;
+	margin-bottom : 5px;
+	text-align : justify;
+}
+
+.pinfos .description {
+	padding : 10px;
+}
+
+.pinfos .download {
+	margin : 200px;
+}
+
+.label {
+	color : #555;
+	width : 80px;
+	float : left;
+}
+
+
+</style>
+</head>
+
+<body>
+
+<div class="page">
+
+<h1><a href="/">lib.haxe.org</a></h1>
+
+<div class="menu">
+	<div class="title">Projects :</div>
+	<ul>
+	::foreach projects::
+	<li><a href="/p/::name::">::name::</a></li>
+	::end::
+	</ul>
+</div>
+
+
+<div class="content">
+
+::if error::
+
+<div class="error">
+	::error::
+</div>
+
+::elseif act_index::
+
+<h2>Welcome</h2>
+
+<p>
+	This website is listing all the libraries available through the <code>haxelib</code> haXe package manager.
+	Please visit <a href="http://haxe.org/haxelib">the haxelib page</a> on haXe website to learn more about haxelib.
+</p>
+
+<h2>Latest releases</h2>
+
+<div class="versions">
+<ul>
+	::foreach versions::
+	<li>
+		<div class="date">::date:: </div>
+		<div class="project"><a href="/p/::(__project.name)::">::(__project.name)::</a> </div>
+		<div class="name">::name:: </div>
+		<div class="download"><a href="$$download(__project,__current__)">Download</a></div>
+		<div class="comments">::comments::</div>
+	</li>
+	::end::
+</ul>
+</div>
+
+
+::elseif act_p::
+
+<h2>::(p.name)::</h2>
+
+<div class="pinfos">
+	<div class="description">::(p.description)::</div>
+	<div class="url"><div class="label">Website</div> <a href="::(p.website)::">::(p.website)::</a></div>
+	<div class="version"><div class="label">Version</div> ::(version.name)::</div>
+	<div class="owner"><div class="label">Owner</div> <a href="/u/::(owner.name)::">::(owner.name)::</a></div>
+	<div class="license"><div class="label">License</div> ::(p.license)::</div>
+	<div class="download"><a href="$$download(p,version)">Download</a></div>
+</div>
+
+<h2>History</h2>
+
+<div class="versions">
+<ul>
+	::foreach versions::
+	<li>
+		<div class="date">::date:: </div>
+		<div class="name">::name:: </div>
+		<div class="download"><a href="$$download(p,__current__)">Download</a></div>
+		<div class="comments">::comments::</div>
+	</li>
+	::end::
+</ul>
+</div>
+
+::elseif act_u::
+
+<h2>::(u.name)::</h2>
+
+<div class="uinfos">
+	<div class="name"><div class="label">Name</div> ::(u.fullname)::</div>
+	<div class="email"><div class="label">Email</div> ::(u.email)::</div>
+</div>
+
+<h2>Projects</h2>
+
+<div class="projects">
+<ul>
+::foreach uprojects::
+	<li><a href="/p/::name::">::name::</a></li>
+::end::
+</ul>
+</div>
+
+::else::
+
+<p>
+	No content for this action
+</p>
+
+::end::
+
+</div>
+</div>
+
+</body>
+</html>