[haiku-depot-web] [haiku-depot-web-app] 7 new revisions pushed by haiku.li...@xxxxxxxxx on 2014-02-09 10:42 GMT

  • From: haiku-depot-web-app@xxxxxxxxxxxxxx
  • To: haiku-depot-web@xxxxxxxxxxxxx
  • Date: Sun, 09 Feb 2014 10:43:15 +0000

master moved from 43ab6c1b9f0e to 68df28d58697

7 new revisions:

Revision: 101f6cb61687
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  8 09:19:06 2014 UTC
Log:      + better authorization system...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=101f6cb61687

Revision: d02f16acd3fc
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  8 09:30:39 2014 UTC
Log:      + add gui for triggering repository imports
http://code.google.com/p/haiku-depot-web-app/source/detail?r=d02f16acd3fc

Revision: b3fbcf57101d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  8 10:16:22 2014 UTC
Log:      + small changes from checks
http://code.google.com/p/haiku-depot-web-app/source/detail?r=b3fbcf57101d

Revision: 11b560e3e392
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  8 11:31:22 2014 UTC
Log:      + better pagination action directives
http://code.google.com/p/haiku-depot-web-app/source/detail?r=11b560e3e392

Revision: b001fd64677c
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  9 06:49:48 2014 UTC
Log:      + create repository api...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=b001fd64677c

Revision: 04264ff1b771
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  9 10:00:28 2014 UTC
Log:      + add and edit repository functionality
http://code.google.com/p/haiku-depot-web-app/source/detail?r=04264ff1b771

Revision: 68df28d58697
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  9 10:38:52 2014 UTC
Log:      + documentation update...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=68df28d58697

==============================================================================
Revision: 101f6cb61687
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  8 09:19:06 2014 UTC
Log:      + better authorization system
+ list repositories has option to see disabled data
+ enable and disable actions on view repository

http://code.google.com/p/haiku-depot-web-app/source/detail?r=101f6cb61687

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/AuthorizationTargetType.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/CheckAuthorizationRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/CheckAuthorizationResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/GetRepositoryRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/GetRepositoryResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/TriggerImportRepositoryRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/TriggerImportRepositoryResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/TargetType.java
 /haikudepotserver-webapp/src/main/webapp/css/listrepositories.css
/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicator.html /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpermissiondirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpkgpermissiondirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifrepositorypermissiondirective.js
 /haikudepotserver-webapp/src/test/resources/logback.xml
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesResult.java
 /haikudepotserver-parent/pom.xml
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
 /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css
 /haikudepotserver-webapp/src/main/webapp/css/home.css
/haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicatordirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js
 /haikudepotserver-webapp/src/main/webapp/js/app/routes.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js
 /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js
/haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/MiscelaneousApiIT.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/AuthorizationTargetType.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model;
+
+/**
+ * <p>This enum is used to identify what type of target the authorization applies to.</p>
+ */
+
+public enum AuthorizationTargetType {
+    PKG,
+    USER,
+    REPOSITORY
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/CheckAuthorizationRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType;
+
+import java.util.List;
+
+public class CheckAuthorizationRequest {
+
+    public List<AuthorizationTargetAndPermission> targetAndPermissions;
+
+    public static class AuthorizationTargetAndPermission {
+
+        /**
+ * <p>The target type defines what sort of object you want to check your authorization for. The #targetIdentifier
+         * then identifies an instance of that type.</p>
+         */
+
+        public AuthorizationTargetType targetType;
+
+        /**
+ * This identifier will identify an instance of the #targetType that has the authorization applied to it. Some + * permissions may not require a target identifier; in which case this value can be supplied as null.
+         */
+
+        public String targetIdentifier;
+
+        /**
+ * <p>This is a list of permissions that the client would like to check for in the context of the target
+         * identified by other parameters in this request.</p>
+         */
+
+        public String permissionCode;
+
+        public AuthorizationTargetAndPermission() {
+        }
+
+ public AuthorizationTargetAndPermission(AuthorizationTargetType targetType, String targetIdentifier, String permissionCode) {
+            this.targetType = targetType;
+            this.targetIdentifier = targetIdentifier;
+            this.permissionCode = permissionCode;
+        }
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/CheckAuthorizationResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType;
+
+import java.util.List;
+
+public class CheckAuthorizationResult {
+
+    public List<AuthorizationTargetAndPermission> targetAndPermissions;
+
+    public static class AuthorizationTargetAndPermission {
+
+        /**
+ * <p>The target type defines what sort of object you want to check your authorization for. The #targetIdentifier
+         * then identifies an instance of that type.</p>
+         */
+
+        public AuthorizationTargetType targetType;
+
+        /**
+ * This identifier will identify an instance of the #targetType that has the authorization applied to it. Some + * permissions may not require a target identifier; in which case this value can be supplied as null.
+         */
+
+        public String targetIdentifier;
+
+        /**
+ * <p>This is a list of permissions that the client would like to check for in the context of the target
+         * identified by other parameters in this request.</p>
+         */
+
+        public String permissionCode;
+
+        /**
+ * <p>This boolean will be true if the target is authorized; false if not.</p>
+         */
+
+        public Boolean authorized;
+    }
+
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/GetRepositoryRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+public class GetRepositoryRequest {
+
+    public String code;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/GetRepositoryResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+public class GetRepositoryResult {
+
+    public Boolean active;
+    public String code;
+    public String architectureCode;
+    public Long createTimestamp;
+    public Long modifyTimestamp;
+    public String url;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/TriggerImportRepositoryRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+public class TriggerImportRepositoryRequest {
+
+    public String code;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/TriggerImportRepositoryResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+public class TriggerImportRepositoryResult {
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+import java.util.List;
+
+public class UpdateRepositoryRequest {
+
+    public enum Filter {
+        ACTIVE
+    };
+
+    public String code;
+
+    public Boolean active;
+
+    public List<Filter> filter;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+public class UpdateRepositoryResult {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.security;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import org.apache.cayenne.DataObject;
+import org.apache.cayenne.ObjectContext;
+import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType;
+import org.haikuos.haikudepotserver.dataobjects.Pkg;
+import org.haikuos.haikudepotserver.dataobjects.Repository;
+import org.haikuos.haikudepotserver.dataobjects.User;
+import org.haikuos.haikudepotserver.security.model.Permission;
+import org.haikuos.haikudepotserver.security.model.TargetType;
+import org.springframework.stereotype.Service;
+
+/**
+ * <P>This class will provide functions around authorization. Some of the model for this is provided
+ * by the API objects.</p>
+ */
+
+@Service
+public class AuthorizationService {
+
+ private AuthorizationTargetType deriveTargetType(DataObject dataObject) {
+        if(null==dataObject)
+            return null;
+
+        if(User.class.isAssignableFrom(dataObject.getClass())) {
+            return AuthorizationTargetType.USER;
+        }
+
+        if(Pkg.class.isAssignableFrom(dataObject.getClass())) {
+            return AuthorizationTargetType.PKG;
+        }
+
+        if(Repository.class.isAssignableFrom(dataObject.getClass())) {
+            return AuthorizationTargetType.REPOSITORY;
+        }
+
+ throw new IllegalStateException("the data object type '"+dataObject.getClass().getSimpleName()+"' is not handled");
+    }
+
+    public boolean check(
+            ObjectContext objectContext,
+            User authenticatedUser,
+            TargetType targetType,
+            String targetIdentifier,
+            Permission permission) {
+        Preconditions.checkNotNull(permission);
+        Preconditions.checkNotNull(objectContext);
+
+        DataObject target = null;
+
+        if(null!=targetType) {
+
+            Optional<? extends DataObject> targetOptional = null;
+
+            if(Strings.isNullOrEmpty(targetIdentifier)) {
+ throw new IllegalStateException("the target type is supplied, but no target identifier");
+            }
+
+            switch(targetType) {
+                case PKG:
+ targetOptional = Pkg.getByName(objectContext, targetIdentifier);
+                    break;
+
+                case REPOSITORY:
+ targetOptional = Repository.getByCode(objectContext, targetIdentifier);
+                    break;
+
+                case USER:
+ targetOptional = User.getByNickname(objectContext, targetIdentifier);
+                    break;
+
+                default:
+ throw new IllegalStateException("the target type is not handled; "+targetType.name());
+            }
+
+ // if the object was not able to be found then we should bail-out and say that the permission
+            // does not apply.
+
+            if(!targetOptional.isPresent()) {
+                return false;
+            }
+
+            target = targetOptional.get();
+        }
+        else {
+            if(!Strings.isNullOrEmpty(targetIdentifier)) {
+ throw new IllegalStateException("the target type was supplied, but not the target identifier");
+            }
+        }
+
+        return check(objectContext, authenticatedUser, target, permission);
+    }
+
+    /**
+ * <p>This method will return true if the permission applies in this situation.</p>
+     */
+
+    public boolean check(
+            ObjectContext objectContext,
+            User authenticatedUser,
+            DataObject target,
+            Permission permission) {
+
+        Preconditions.checkNotNull(permission);
+        Preconditions.checkNotNull(objectContext);
+ Preconditions.checkState(deriveTargetType(target) == permission.getRequiredTargetType());
+
+        switch(permission) {
+
+            case REPOSITORY_EDIT:
+            case REPOSITORY_IMPORT:
+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+
+            case REPOSITORY_VIEW:
+                Repository repository = (Repository) target;
+ return repository.getActive() || (null!=authenticatedUser && authenticatedUser.getIsRoot());
+
+            case REPOSITORY_LIST:
+                return true;
+
+            case REPOSITORY_LIST_INACTIVE:
+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+
+            case USER_VIEW:
+            case USER_EDIT:
+            case USER_CHANGEPASSWORD:
+                return
+                        null!=authenticatedUser
+ && (authenticatedUser.getIsRoot() || authenticatedUser.equals(target));
+
+            case USER_LIST:
+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+
+            case PKG_EDITICON:
+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+
+            default:
+ throw new IllegalStateException("unhandled permission; "+permission.name());
+        }
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.security.model;
+
+import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType;
+
+public enum Permission {
+
+    REPOSITORY_VIEW(AuthorizationTargetType.REPOSITORY),
+    REPOSITORY_EDIT(AuthorizationTargetType.REPOSITORY),
+    REPOSITORY_IMPORT(AuthorizationTargetType.REPOSITORY),
+    REPOSITORY_LIST(null),
+    REPOSITORY_LIST_INACTIVE(null),
+
+    USER_VIEW(AuthorizationTargetType.USER),
+    USER_EDIT(AuthorizationTargetType.USER),
+    USER_CHANGEPASSWORD(AuthorizationTargetType.USER),
+    USER_LIST(null),
+
+    PKG_EDITICON(AuthorizationTargetType.PKG);
+
+    private AuthorizationTargetType requiredTargetType;
+
+    Permission(AuthorizationTargetType requiredTargetType) {
+        this.requiredTargetType = requiredTargetType;
+    }
+
+    public AuthorizationTargetType getRequiredTargetType() {
+        return requiredTargetType;
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/TargetType.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.security.model;
+
+/**
+ * <p>This enum defines the entities onto which the authorization might occur.</p>
+ */
+
+public enum TargetType {
+    PKG,
+    USER,
+    REPOSITORY
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/css/listrepositories.css Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,16 @@
+/*
+This specific layout works off the premise that there is only one table on this page.
+*/
+
+.list-repositories .table-general > tbody > tr > td:nth-child(1) {
+    width: 10%;
+    text-align: center;
+}
+
+.list-repositories .table-general > tbody > tr > td:nth-child(2) {
+    width:60%;
+}
+
+.list-repositories .table-general > tbody > tr > td:nth-child(3) {
+    width:30%;
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,36 @@
+<breadcrumbs items="breadcrumbItems"></breadcrumbs>
+
+<div class="content-container">
+
+    <dl>
+        <dt>Code</dt>
+        <dd><code>{{repository.code}}</code>&nbsp;</dd>
+        <dt>Active</dt>
+ <dd><active-indicator state="repository.active"></active-indicator></dd>
+        <dt>URL</dt>
+        <dd><a href="{{repository.code}}">{{repository.url}}</a></dd>
+        <dt>Architecture</dt>
+        <dd>{{repository.architectureCode}}</dd>
+        <dt>Created</dt>
+        <dd>{{repository.createTimestamp|timestamp}}</dd>
+        <dt>Modified</dt>
+        <dd>{{repository.modifyTimestamp|timestamp}}</dd>
+    </dl>
+
+    <ul>
+ <li ng-show="canDeactivate()" repository="repository" show-if-repository-permission="'REPOSITORY_EDIT'">
+            <a href="" ng-click="goDeactivate()">Deactivate</a>
+        </li>
+ <li ng-show="canActivate()" repository="repository" show-if-repository-permission="'REPOSITORY_EDIT'">
+            <a href="" ng-click="goActivate()">Reactivate</a>
+        </li>
+ <li ng-show="repository.active" repository="repository" show-if-repository-permission="'REPOSITORY_IMPORT'">
+            <a href="" ng-click="goTriggerImport()">Trigger import</a>
+        </li>
+    </ul>
+
+</div>
+
+<div class="footer"></div>
+<spinner spin="shouldSpin()"></spinner>
+
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'ViewRepositoryController',
+    [
+        '$scope','$log','$location','$routeParams',
+        'jsonRpc','constants','userState','errorHandling','breadcrumbs',
+        function(
+            $scope,$log,$location,$routeParams,
+            jsonRpc,constants,userState,errorHandling,breadcrumbs) {
+
+            $scope.breadcrumbItems = undefined;
+            $scope.repository = undefined;
+            var amUpdatingActive = false;
+
+            refetchRepository();
+
+            $scope.shouldSpin = function() {
+                return undefined == $scope.repository;
+            }
+
+            function updateActive(flag) {
+
+                amUpdatingActive = true;
+
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_REPOSITORY,
+                        "updateRepository",
+                        [{
+                            code : $routeParams.code,
+                            active : flag,
+                            filter : [ 'ACTIVE' ]
+                        }]
+                    ).then(
+                    function(result) {
+                        amUpdatingActive = false;
+                        $scope.repository.active = flag;
+ $log.info('did set the active flag on '+$scope.repository.code+' to '+flag);
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            }
+
+            $scope.canActivate = function() {
+ return $scope.repository && !$scope.repository.active && !amUpdatingActive;
+            }
+
+            $scope.canDeactivate = function() {
+ return $scope.repository && $scope.repository.active && !amUpdatingActive;
+            }
+
+            $scope.goActivate = function() {
+                updateActive(true);
+            }
+
+            $scope.goDeactivate = function() {
+                updateActive(false);
+            }
+
+            /**
+ * <p>This function will initiate an import of a repository. These run sequentially so it may not happen
+             * immediately; it may be queued to go later.</p>
+             */
+
+            $scope.goTriggerImport = function() {
+
+            }
+
+            function refreshBreadcrumbItems() {
+                $scope.breadcrumbItems = [
+                    breadcrumbs.createMore(),
+                    breadcrumbs.createListRepositories(),
+                    breadcrumbs.createViewRepository($scope.repository)
+                ];
+            }
+
+            function refetchRepository() {
+
+                $scope.repository = undefined;
+
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_REPOSITORY,
+                        "getRepository",
+                        [{ code: $routeParams.code }]
+                    ).then(
+                    function(result) {
+                        $scope.repository = result;
+ $log.info('found '+$scope.repository.code+' repository');
+                        refreshBreadcrumbItems();
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            }
+
+        }
+    ]
+);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicator.html Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,1 @@
+<svg height="12" width="12"><circle cx="6" cy="6" r="6" fill="gray" ng-class="classes"></circle></svg>
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpermissiondirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This directive is able to display the transcluded (material inside the element) if the permission holds. Note that
+ * this variant does not have a specific target.</p>
+ */
+
+angular.module('haikudepotserver').directive('showIfPermission',[
+    'userState', function(userState) {
+        return {
+            restrict: 'A',
+            link : function($scope,element,attributes) {
+
+ var permissionCodeExpression = attributes['showIfPermission']; + var permissionCode = $scope.$eval(permissionCodeExpression);
+
+                // by default we will hide it.
+
+                element.addClass('app-hide');
+                check();
+
+ $scope.$watch(permissionCodeExpression, function(newValue,oldValue) {
+                    permissionCode = newValue;
+                    check();
+                });
+
+                function check() {
+                    if(!permissionCode) {
+                        element.addClass('app-hide');
+                    }
+                    else {
+                        var targetAndPermissions = [];
+
+                        if(angular.isArray(permissionCode)) {
+                            _.each(permissionCode, function(item) {
+                                targetAndPermissions.push({
+                                    targetType: null,
+                                    targetIdentifier : null,
+                                    permissionCode : item
+                                });
+                            });
+                        }
+                        else {
+                            targetAndPermissions.push({
+                                targetType: null,
+                                targetIdentifier : null,
+                                permissionCode : ''+permissionCode
+                            });
+                        }
+
+ userState.areAuthorized(targetAndPermissions).then(function(flag) {
+                            if(flag) {
+                                element.removeClass('app-hide');
+                            }
+                            else {
+                                element.addClass('app-hide');
+                            }
+                        });
+                    }
+                }
+
+            }
+        }
+    }
+]);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpkgpermissiondirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This directive is able to display the transcluded (material inside the element) if the permission holds against
+ * the nominated package.</p>
+ */
+
+angular.module('haikudepotserver').directive('showIfPkgPermission',[
+    'userState', function(userState) {
+        return {
+            restrict: 'A',
+            link : function($scope,element,attributes) {
+
+                var pkgExpression = attributes['pkg'];
+ var permissionCodeExpression = attributes['showIfPkgPermission'];
+                var pkg = $scope.$eval(pkgExpression);
+ var permissionCode = $scope.$eval(permissionCodeExpression);
+
+                // by default we will hide it.
+
+                element.addClass('app-hide');
+                check();
+
+                $scope.$watch(pkgExpression, function(newValue,oldValue) {
+                   pkg = newValue;
+                    check();
+                });
+
+ $scope.$watch(permissionCodeExpression, function(newValue,oldValue) {
+                    permissionCode = newValue;
+                    check();
+                });
+
+                function check() {
+                    if(!permissionCode || !pkg) {
+                        element.addClass('app-hide');
+                    }
+                    else {
+                        var targetAndPermissions = [];
+
+                        if(angular.isArray(permissionCode)) {
+                            _.each(permissionCode, function(item) {
+                                targetAndPermissions.push({
+                                    targetType: 'PKG',
+                                    targetIdentifier : pkg.name,
+                                    permissionCode : item
+                                });
+                            });
+                        }
+                        else {
+                            targetAndPermissions.push({
+                                targetType: 'PKG',
+                                targetIdentifier : pkg.name,
+                                permissionCode : permissionCode
+                            });
+                        }
+
+ userState.areAuthorized(targetAndPermissions).then(function(flag) {
+                            if(flag) {
+                                element.removeClass('app-hide');
+                            }
+                            else {
+                                element.addClass('app-hide');
+                            }
+                        });
+                    }
+                }
+
+            }
+        }
+    }
+]);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifrepositorypermissiondirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This directive is able to display the transcluded (material inside the element) if the permission holds against
+ * the nominated repository.</p>
+ */
+
+angular.module('haikudepotserver').directive('showIfRepositoryPermission',[
+    'userState', function(userState) {
+        return {
+            restrict: 'A',
+            link : function($scope,element,attributes) {
+
+                var repositoryExpression = attributes['repository'];
+ var permissionCodeExpression = attributes['showIfRepositoryPermission'];
+                var repository = $scope.$eval(repositoryExpression);
+ var permissionCode = $scope.$eval(permissionCodeExpression);
+
+                // by default we will hide it.
+
+                element.addClass('app-hide');
+                check();
+
+ $scope.$watch(repositoryExpression, function(newValue,oldValue) {
+                    repository = newValue;
+                    check();
+                });
+
+ $scope.$watch(permissionCodeExpression, function(newValue,oldValue) {
+                    permissionCode = newValue;
+                    check();
+                });
+
+                function check() {
+                    if(!permissionCode || !repository) {
+                        element.addClass('app-hide');
+                    }
+                    else {
+                        var targetAndPermissions = [];
+
+                        if(angular.isArray(permissionCode)) {
+                            _.each(permissionCode, function(item) {
+                                targetAndPermissions.push({
+                                    targetType: 'REPOSITORY',
+                                    targetIdentifier : repository.code,
+                                    permissionCode : item
+                                });
+                            });
+                        }
+                        else {
+                            targetAndPermissions.push({
+                                targetType: 'REPOSITORY',
+                                targetIdentifier : repository.code,
+                                permissionCode : permissionCode
+                            });
+                        }
+
+ userState.areAuthorized(targetAndPermissions).then(function(flag) {
+                            if(flag) {
+                                element.removeClass('app-hide');
+                            }
+                            else {
+                                element.addClass('app-hide');
+                            }
+                        });
+                    }
+                }
+
+            }
+        }
+    }
+]);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/resources/logback.xml Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,22 @@
+<configuration>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <!-- encoders are assigned the type
+ ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+        <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <!--
+ Cayenne logs quite a bit of material out; best to turn that quantity of logging down a bit.
+    -->
+
+    <logger name="org.apache.cayenne" level="warn"/>
+    <logger name="com.googlecode.flyway" level="info"/>
+
+    <root level="info">
+        <appender-ref ref="STDOUT" />
+    </root>
+
+</configuration>
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java Sat Feb 8 09:19:06 2014 UTC
@@ -11,6 +11,13 @@
 @JsonRpcService("/api/v1/miscellaneous")
 public interface MiscellaneousApi {

+    /**
+ * <p>This method will take in a list of permissions with targets and will return the list of those that + * pass authorization checks against the presently authenticated user.</p>
+     */
+
+ CheckAuthorizationResult checkAuthorization(CheckAuthorizationRequest deriveAuthorizationRequest);
+
     /**
* <p>This method will raise a runtime exception to test the behaviour of the server and client in this
      * situation.</p>
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sat Feb 8 09:19:06 2014 UTC
@@ -6,8 +6,8 @@
 package org.haikuos.haikudepotserver.api1;

 import com.googlecode.jsonrpc4j.JsonRpcService;
-import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesRequest; -import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesResult;
+import org.haikuos.haikudepotserver.api1.model.repository.*;
+import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;

 @JsonRpcService("/api/v1/repository")
 public interface RepositoryApi {
@@ -19,4 +19,24 @@

SearchRepositoriesResult searchRepositories(SearchRepositoriesRequest searchRepositoriesRequest);

+    /**
+ * <p>This method will return the repository details for the repository identified by the
+     * code in the request object.</p>
+     */
+
+ GetRepositoryResult getRepository(GetRepositoryRequest getRepositoryRequest) throws ObjectNotFoundException;
+
+    /**
+ * <p>This method will update the repository. As well as the data to update, it also includes a 'filter' that
+     * defines the fields that should be updated in this request.</p>
+     */
+
+ UpdateRepositoryResult updateRepository(UpdateRepositoryRequest updateRepositoryRequest) throws ObjectNotFoundException;
+
+    /**
+     * <p>This method will trigger the import process for a repository.</p>
+     */
+
+ TriggerImportRepositoryResult triggerImportRepositoryResult(TriggerImportRepositoryRequest triggerImportRepositoryRequest) throws ObjectNotFoundException;
+
 }
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java Thu Dec 5 09:23:22 2013 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -28,13 +28,6 @@

     public Boolean hasIcon;

-    /**
- * <p>This field will be true if the user who is presently authenticated would be able to
-     * edit the package.</p>
-     */
-
-    public Boolean canEdit;
-
     public List<Version> versions;

     public static class Version {
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesRequest.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -8,4 +8,7 @@
 import org.haikuos.haikudepotserver.api1.support.AbstractSearchRequest;

 public class SearchRepositoriesRequest extends AbstractSearchRequest {
+
+    public Boolean includeInactive;
+
 }
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesResult.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -13,10 +13,7 @@

         public Boolean active;
         public String code;
-        public String url;
         public String architectureCode;
-        public Long createTimestamp;
-        public Long modifyTimestamp;

     }

=======================================
--- /haikudepotserver-parent/pom.xml    Sat Jan 18 09:59:17 2014 UTC
+++ /haikudepotserver-parent/pom.xml    Sat Feb  8 09:19:06 2014 UTC
@@ -47,7 +47,7 @@
             <dependency>
                 <groupId>com.google.guava</groupId>
                 <artifactId>guava</artifactId>
-                <version>15.0</version>
+                <version>16.0.1</version>
             </dependency>

             <!-- WEB / API -->
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java Sat Feb 8 09:19:06 2014 UTC
@@ -5,18 +5,18 @@

 package org.haikuos.haikudepotserver.api1;

-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
+import com.google.common.base.*;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.security.model.Permission;
 import org.haikuos.haikudepotserver.api1.model.miscellaneous.*;
 import org.haikuos.haikudepotserver.dataobjects.Architecture;
 import org.haikuos.haikudepotserver.dataobjects.User;
+import org.haikuos.haikudepotserver.security.AuthorizationService;
+import org.haikuos.haikudepotserver.security.model.TargetType;
 import org.haikuos.haikudepotserver.support.Closeables;
 import org.haikuos.haikudepotserver.support.RuntimeInformationService;
 import org.slf4j.Logger;
@@ -40,8 +40,42 @@
     @Resource
     ServerRuntime serverRuntime;

+    @Resource
+    AuthorizationService authorizationService;
+
     @Resource
     RuntimeInformationService runtimeInformationService;
+
+    @Override
+ public CheckAuthorizationResult checkAuthorization(CheckAuthorizationRequest deriveAuthorizationRequest) {
+
+        Preconditions.checkNotNull(deriveAuthorizationRequest);
+ Preconditions.checkNotNull(deriveAuthorizationRequest.targetAndPermissions);
+
+        final ObjectContext context = serverRuntime.getContext();
+        CheckAuthorizationResult result = new CheckAuthorizationResult();
+        result.targetAndPermissions = Lists.newArrayList();
+
+ for(CheckAuthorizationRequest.AuthorizationTargetAndPermission targetAndPermission : deriveAuthorizationRequest.targetAndPermissions) {
+
+ CheckAuthorizationResult.AuthorizationTargetAndPermission authorizationTargetAndPermission = new CheckAuthorizationResult.AuthorizationTargetAndPermission();
+
+ authorizationTargetAndPermission.permissionCode = targetAndPermission.permissionCode; + authorizationTargetAndPermission.targetIdentifier = targetAndPermission.targetIdentifier; + authorizationTargetAndPermission.targetType = targetAndPermission.targetType;
+
+ authorizationTargetAndPermission.authorized = authorizationService.check(
+                    context,
+                    tryObtainAuthenticatedUser(context).orNull(),
+ null!=targetAndPermission.targetType ? TargetType.valueOf(targetAndPermission.targetType.name()) : null,
+                    targetAndPermission.targetIdentifier,
+ Permission.valueOf(targetAndPermission.permissionCode));
+
+ result.targetAndPermissions.add(authorizationTargetAndPermission);
+        }
+
+        return result;
+    }

     @Override
public RaiseExceptionResult raiseException(RaiseExceptionRequest raiseExceptionRequest) {
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Sat Feb 8 09:19:06 2014 UTC
@@ -18,9 +18,11 @@
 import org.haikuos.haikudepotserver.api1.model.pkg.*;
import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
 import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+import org.haikuos.haikudepotserver.security.model.Permission;
 import org.haikuos.haikudepotserver.dataobjects.*;
 import org.haikuos.haikudepotserver.pkg.PkgService;
 import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
+import org.haikuos.haikudepotserver.security.AuthorizationService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
@@ -41,6 +43,9 @@
     @Resource
     ServerRuntime serverRuntime;

+    @Resource
+    AuthorizationService authorizationService;
+
     @Resource
     PkgService pkgService;

@@ -208,7 +213,6 @@

         result.name = pkgOptional.get().getName();
result.modifyTimestamp = pkgOptional.get().getModifyTimestamp().getTime(); - result.canEdit = pkgOptional.get().canBeEditedBy(tryObtainAuthenticatedUser(context).orNull());
         result.hasIcon = !pkgOptional.get().getPkgIcons().isEmpty();

         switch(request.versionType) {
@@ -257,8 +261,8 @@

         User user = obtainAuthenticatedUser(context);

-        if(!pkgOptional.get().canBeEditedBy(user)) {
- logger.warn("attempt to remove the icon for package {}, but the user {} is not able to",pkgOptional.get().getName(),user.getNickname()); + if(!authorizationService.check(context, user, pkgOptional.get(), Permission.PKG_EDITICON)) { + logger.warn("attempt to remove the icon for package {}, but the user {} is not able to", pkgOptional.get().getName(), user.getNickname());
             throw new AuthorizationFailureException();
         }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sat Feb 8 09:19:06 2014 UTC
@@ -6,37 +6,104 @@
 package org.haikuos.haikudepotserver.api1;

 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesRequest; -import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesResult;
+import org.haikuos.haikudepotserver.api1.model.repository.*;
+import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
+import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
 import org.haikuos.haikudepotserver.dataobjects.Repository;
+import org.haikuos.haikudepotserver.dataobjects.User;
+import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob;
 import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
+import org.haikuos.haikudepotserver.repository.RepositoryImportService;
 import org.haikuos.haikudepotserver.repository.RepositoryService;
import org.haikuos.haikudepotserver.repository.model.RepositorySearchSpecification;
+import org.haikuos.haikudepotserver.security.AuthorizationService;
+import org.haikuos.haikudepotserver.security.model.Permission;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;

 import javax.annotation.Resource;
+import javax.naming.ldap.PagedResultsControl;
 import java.util.List;

 @Component
public class RepositoryApiImpl extends AbstractApiImpl implements RepositoryApi {

+ protected static Logger logger = LoggerFactory.getLogger(RepositoryApiImpl.class);
+
     @Resource
     ServerRuntime serverRuntime;

+    @Resource
+    AuthorizationService authorizationService;
+
     @Resource
     RepositoryService repositoryService;

+    @Resource
+    RepositoryImportService repositoryImportService;
+
+    // note; no integration test for this one.
+    @Override
+    public TriggerImportRepositoryResult triggerImportRepositoryResult(
+            TriggerImportRepositoryRequest triggerImportRepositoryRequest)
+            throws ObjectNotFoundException {
+
+        Preconditions.checkNotNull(triggerImportRepositoryRequest);
+ Preconditions.checkState(!Strings.isNullOrEmpty(triggerImportRepositoryRequest.code));
+
+        final ObjectContext context = serverRuntime.getContext();
+
+ Optional<Repository> repositoryOptional = Repository.getByCode(context, triggerImportRepositoryRequest.code);
+
+        if(!repositoryOptional.isPresent()) {
+ throw new ObjectNotFoundException(Repository.class.getSimpleName(), triggerImportRepositoryRequest.code);
+        }
+
+        if(!authorizationService.check(
+                context,
+                tryObtainAuthenticatedUser(context).orNull(),
+                repositoryOptional.get(),
+                Permission.REPOSITORY_IMPORT)) {
+            throw new AuthorizationFailureException();
+        }
+
+ repositoryImportService.submit(new PkgRepositoryImportJob(repositoryOptional.get().getCode()));
+
+        return new TriggerImportRepositoryResult();
+    }
+
+
     @Override
public SearchRepositoriesResult searchRepositories(SearchRepositoriesRequest request) {
         Preconditions.checkNotNull(request);

         final ObjectContext context = serverRuntime.getContext();
+
+        if(!authorizationService.check(
+                context,
+                tryObtainAuthenticatedUser(context).orNull(),
+                null,
+                Permission.REPOSITORY_LIST)) {
+            throw new AuthorizationFailureException();
+        }
+
+        if(null!=request.includeInactive && request.includeInactive) {
+            if(!authorizationService.check(
+                    context,
+                    tryObtainAuthenticatedUser(context).orNull(),
+                    null,
+                    Permission.REPOSITORY_LIST_INACTIVE)) {
+                throw new AuthorizationFailureException();
+            }
+        }

RepositorySearchSpecification specification = new RepositorySearchSpecification();
         String exp = request.expression;
@@ -54,6 +121,7 @@

specification.setLimit(request.limit+1); // get +1 to see if there are any more.
         specification.setOffset(request.offset);
+ specification.setIncludeInactive(null!=request.includeInactive && request.includeInactive);

         SearchRepositoriesResult result = new SearchRepositoriesResult();
List<Repository> searchedRepositories = repositoryService.search(context,specification);
@@ -75,8 +143,6 @@
                         resultRepository.active = input.getActive();
resultRepository.architectureCode = input.getArchitecture().getCode();
                         resultRepository.code = input.getCode();
- resultRepository.createTimestamp = input.getCreateTimestamp().getTime(); - resultRepository.modifyTimestamp = input.getModifyTimestamp().getTime();
                         return resultRepository;
                     }
                 }
@@ -84,5 +150,84 @@

         return result;
     }
+
+    @Override
+ public GetRepositoryResult getRepository(GetRepositoryRequest getRepositoryRequest) throws ObjectNotFoundException {
+        Preconditions.checkNotNull(getRepositoryRequest);
+ Preconditions.checkState(!Strings.isNullOrEmpty(getRepositoryRequest.code));
+
+        final ObjectContext context = serverRuntime.getContext();
+
+ Optional<Repository> repositoryOptional = Repository.getByCode(context, getRepositoryRequest.code);
+
+        if(!repositoryOptional.isPresent()) {
+ throw new ObjectNotFoundException(Repository.class.getSimpleName(), getRepositoryRequest.code);
+        }
+
+        if(!authorizationService.check(
+                context,
+                tryObtainAuthenticatedUser(context).orNull(),
+                repositoryOptional.get(),
+                Permission.REPOSITORY_VIEW)) {
+            throw new AuthorizationFailureException();
+        }
+
+        GetRepositoryResult result = new GetRepositoryResult();
+        result.active = repositoryOptional.get().getActive();
+ result.architectureCode = repositoryOptional.get().getArchitecture().getCode();
+        result.code = repositoryOptional.get().getCode();
+ result.createTimestamp = repositoryOptional.get().getCreateTimestamp().getTime(); + result.modifyTimestamp = repositoryOptional.get().getModifyTimestamp().getTime();
+        result.url = repositoryOptional.get().getUrl();
+
+        return result;
+    }
+
+    @Override
+ public UpdateRepositoryResult updateRepository(UpdateRepositoryRequest updateRepositoryRequest) throws ObjectNotFoundException {
+        Preconditions.checkNotNull(updateRepositoryRequest);
+
+        final ObjectContext context = serverRuntime.getContext();
+
+ Optional<Repository> repositoryOptional = Repository.getByCode(context, updateRepositoryRequest.code);
+
+        if(!repositoryOptional.isPresent()) {
+ throw new ObjectNotFoundException(Repository.class.getSimpleName(), updateRepositoryRequest.code);
+        }
+
+        authorizationService.check(
+                context,
+                tryObtainAuthenticatedUser(context).orNull(),
+                repositoryOptional.get(),
+                Permission.REPOSITORY_EDIT);
+
+ for(UpdateRepositoryRequest.Filter filter : updateRepositoryRequest.filter) {
+            switch(filter) {
+                case ACTIVE:
+                    if(null==updateRepositoryRequest.active) {
+ throw new IllegalStateException("the active flag must be supplied");
+                    }
+
+ if(repositoryOptional.get().getActive() != updateRepositoryRequest.active) { + repositoryOptional.get().setActive(updateRepositoryRequest.active); + logger.info("did set the active flag on repository {} to {}", updateRepositoryRequest.code,updateRepositoryRequest.active);
+                    }
+
+                    break;
+
+                default:
+ throw new IllegalStateException("unhandled filter for updating a repository");
+            }
+        }
+
+        if(context.hasChanges()) {
+            context.commitChanges();
+        }
+        else {
+ logger.info("update repository {} with no changes made", updateRepositoryRequest.code);
+        }
+
+        return new UpdateRepositoryResult();
+    }

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Sat Feb 8 09:19:06 2014 UTC
@@ -11,11 +11,13 @@
 import com.google.common.util.concurrent.Uninterruptibles;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.security.model.Permission;
 import org.haikuos.haikudepotserver.api1.model.user.*;
 import org.haikuos.haikudepotserver.api1.support.*;
 import org.haikuos.haikudepotserver.captcha.CaptchaService;
 import org.haikuos.haikudepotserver.dataobjects.User;
 import org.haikuos.haikudepotserver.security.AuthenticationService;
+import org.haikuos.haikudepotserver.security.AuthorizationService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
@@ -31,6 +33,9 @@
     @Resource
     ServerRuntime serverRuntime;

+    @Resource
+    AuthorizationService authorizationService;
+
     @Resource
     CaptchaService captchaService;

@@ -96,15 +101,15 @@
         final ObjectContext context = serverRuntime.getContext();
         User authUser = obtainAuthenticatedUser(context);

- if(!authUser.getNickname().equals(getUserRequest.nickname) && !authUser.getDerivedCanManageUsers()) {
-            throw new AuthorizationFailureException();
-        }
-
Optional<User> user = User.getByNickname(context, getUserRequest.nickname);

         if(!user.isPresent()) {
throw new ObjectNotFoundException(User.class.getSimpleName(), User.NICKNAME_PROPERTY);
         }
+
+ if(!authorizationService.check(context, authUser, user.get(), Permission.USER_VIEW)) {
+            throw new AuthorizationFailureException();
+        }

         GetUserResult result = new GetUserResult();
         result.nickname = user.get().getNickname();
@@ -176,18 +181,6 @@
                 throw new CaptchaBadResponseException();
             }
         }
-
- // if the logged in user is not root then only the user who has authenticated can change their password.
-
- if(!authUser.getNickname().equals(changePasswordRequest.nickname)) {
-            if(authUser.getIsRoot()) {
-                logger.info("allowing change password for root user");
-            }
-            else {
- logger.info("the logged in user {} is not allowed to change the password of another user {}",authUser.getNickname(),changePasswordRequest.nickname);
-                throw new AuthorizationFailureException();
-            }
-        }

// if the logged in user is non-root then we need to make sure that the old and new passwords
         // match-up.
@@ -218,6 +211,12 @@
         }

         User user = userOptional.get();
+
+ if(!authorizationService.check(context, authUser, userOptional.get(), Permission.USER_CHANGEPASSWORD)) { + logger.info("the logged in user {} is not allowed to change the password of another user {}",authUser.getNickname(),changePasswordRequest.nickname);
+            throw new AuthorizationFailureException();
+        }
+
user.setPasswordHash(authenticationService.hashPassword(user, changePasswordRequest.newPasswordClear));
         context.commitChanges();
logger.info("did change password for user {}", changePasswordRequest.nickname);
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java Mon Jan 20 10:45:32 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java Sat Feb 8 09:19:06 2014 UTC
@@ -76,10 +76,6 @@

         return Optional.absent();
     }
-
-    public boolean canBeEditedBy(User user) {
-        return null!=user && user.getIsRoot();
-    }

     public void setModifyTimestamp() {
         setModifyTimestamp(new java.util.Date());
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Sat Feb 8 09:19:06 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -11,6 +11,8 @@
 import com.google.common.net.MediaType;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.security.model.Permission;
+import org.haikuos.haikudepotserver.security.AuthorizationService;
 import org.haikuos.haikudepotserver.support.ByteCounterOutputStream;
 import org.haikuos.haikudepotserver.support.NoOpOutputStream;
import org.haikuos.haikudepotserver.web.controller.WebResourceGroupController;
@@ -53,6 +55,9 @@
     @Resource
     PkgService pkgService;

+    @Resource
+    AuthorizationService authorizationService;
+
@RequestMapping(value = "/{"+KEY_PKGNAME+"}.{"+KEY_FORMAT+"}", method = RequestMethod.HEAD)
     public void fetchHead(
             HttpServletRequest request,
@@ -175,7 +180,7 @@

         Optional<User> user = tryObtainAuthenticatedUser(context);

-        if(!user.isPresent() || !pkg.get().canBeEditedBy(user.get())) {
+ if(!authorizationService.check(context, user.orNull(), pkg.get(), Permission.PKG_EDITICON)) { logger.warn("attempt to edit the pkg icon, but there is no user present or that user is not able to edit the pkg");
             throw new PkgAuthorizationFailure();
         }
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Feb 8 09:19:06 2014 UTC
@@ -59,6 +59,9 @@
<value>/js/app/directive/userlabeldirective.js</value> <value>/js/app/directive/repositorylabeldirective.js</value> <value>/js/app/directive/activeindicatordirective.js</value> + <value>/js/app/directive/showifpkgpermissiondirective.js</value> + <value>/js/app/directive/showifrepositorypermissiondirective.js</value> + <value>/js/app/directive/showifpermissiondirective.js</value>

<value>/js/app/controller/viewpkgcontroller.js</value> <value>/js/app/controller/homecontroller.js</value>
@@ -71,6 +74,7 @@
<value>/js/app/controller/morecontroller.js</value> <value>/js/app/controller/runtimeinformationcontroller.js</value> <value>/js/app/controller/listrepositoriescontroller.js</value> + <value>/js/app/controller/viewrepositorycontroller.js</value>

<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value>
@@ -99,6 +103,7 @@
                             <value>/css/haikudepotserver.css</value>
                             <value>/css/home.css</value>
                             <value>/css/createuser.css</value>
+                            <value>/css/listrepositories.css</value>
                             <value>/css/viewpkg.css</value>
                             <value>/css/banner.css</value>
                             <value>/css/breadcrumbs.css</value>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sat Feb 8 09:19:06 2014 UTC
@@ -337,20 +337,43 @@

 /*
 ==================================
-ACTIVE / INACTIVE INDICATORS
+ACTIVE / INACTIVE INDICATORS -- SVG
 */

-.active-indicator {
-    background-color: grey;
-    width: 12px;
-    height: 12px;
-    border-radius: 6px;
+.active-indicator.active-indicator-true {
+    fill: darkolivegreen;
+}
+
+.active-indicator.active-indicator-false {
+    fill: firebrick;
+}
+
+/*
+==================================
+DATA LISTS
+*/
+
+dl dt {
+    color:gray;
+    float:left;
+    clear:left;
+    text-align: right;
+    margin-right:10px;
+    padding:5px;
+    width:20%;
 }

-.active-indicator.active-indicator-true {
-    background-color: darkolivegreen;
+dl dd {
+    margin:2px 0;
+    padding:5px 0;
+    width:80%;
 }

-.inactive-indicator.active-indicator-false {
-    background-color: firebrick;
+/*
+==================================
+HIDE / SHOW FOR OWN DIRECTIVES
+*/
+
+.app-hide {
+    display: none;
 }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/home.css Sun Feb 2 09:38:27 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/home.css Sat Feb 8 09:19:06 2014 UTC
@@ -2,15 +2,15 @@
This specific layout works off the premise that there is only one table on this page.
 */

-.table-general > tbody > tr > td:nth-child(1) {
+.home .table-general > tbody > tr > td:nth-child(1) {
     width: 24px;
 }

-.table-general > tbody > tr > td:nth-child(1) {
+.home .table-general > tbody > tr > td:nth-child(1) {
     width:18px;
     text-align: center;
 }

-.table-general > tbody > tr > td:nth-child(2) {
+.home .table-general > tbody > tr > td:nth-child(2) {
     width:50%;
 }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Sat Feb 8 09:19:06 2014 UTC
@@ -118,7 +118,15 @@
                             function() {
                                 $scope.amSaving = false;
$log.info('have set the 32px icon for the pkg '+$scope.pkg.name); - $location.path('/viewpkg/'+$scope.pkg.name+'/latest').search({});
+                                var arch = $location.search()['arch'];
+
+                                if(arch) {
+ $location.path('/viewpkg/'+$scope.pkg.name+'/latest/'+arch).search({});
+                                }
+                                else {
+ $log.info('unable to navigate back to the pkg as no \'arch\' was supplied in the search parameters --> will go home');
+                                    $location.path('/').search({});
+                                }
                             },
                             function(e) {
                                 $scope.amSaving = false;
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Sat Feb 8 09:19:06 2014 UTC
@@ -1,4 +1,4 @@
-<div class="content-container">
+<div class="content-container home">

     <div id="search-criteria-container">
         <div>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sat Feb 8 09:19:06 2014 UTC
@@ -1,6 +1,6 @@
 <breadcrumbs items="breadcrumbItems"></breadcrumbs>

-<div class="content-container">
+<div class="content-container list-repositories">

     <div id="search-criteria-container">
         <div>
@@ -32,21 +32,28 @@

             <table class="table-general">
                 <thead>
+                <th>Active</th>
                 <th>Code</th>
                 <th>Architecture</th>
-                <th>Active</th>
                 </thead>
                 <tbody>
                 <tr ng-repeat="repository in repositories">
+ <td><active-indicator state="repository.active"></active-indicator></td> <td><repository-label repository="repository"></repository-label></td>
                     <td><code>{{repository.architectureCode}}</code></td>
- <td><active-indicator state="repository.active"></active-indicator></td>
                 </tr>
                 </tbody>
             </table>

         </div>
     </div>
+
+    <ul>
+ <li ng-show="!amShowingInactive" show-if-permission="'REPOSITORY_LIST_INACTIVE'"> + <a href="" ng-click="goShowInactive()">Show inactive repositories</a>
+        </li>
+    </ul>
+
 </div>

 <div class="footer"></div>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js Sat Feb 8 09:19:06 2014 UTC
@@ -24,14 +24,19 @@
             $scope.repositories = undefined;
             $scope.hasMore = undefined;
             $scope.offset = 0;
+            $scope.amShowingInactive = false;
+            var amFetchingRepositories = false;

             refetchRepositoriesAtFirstPage();

-            var amFetchingRepositories = false;
-
             $scope.shouldSpin = function() {
                 return amFetchingRepositories;
             }
+
+            $scope.goShowInactive = function() {
+                $scope.amShowingInactive = true;
+                refetchRepositoriesAtFirstPage();
+            }

             // ---- PAGINATION

@@ -55,6 +60,7 @@
                         [{
                             expression : $scope.searchExpression,
                             expressionType : 'CONTAINS',
+                            includeInactive : $scope.amShowingInactive,
                             offset : $scope.offset,
                             limit : PAGESIZE
                         }]
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html Sat Feb 8 09:19:06 2014 UTC
@@ -14,7 +14,7 @@
         <li>
             <a href="" ng-click="goHome()">Home</a>
         </li>
-        <li>
+        <li show-if-permission="'REPOSITORY_LIST'">
             <a href="" ng-click="goListRepositories()">Repositories</a>
         </li>
         <li ng-show="canGoRuntimeInformation">
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Sat Feb 8 09:19:06 2014 UTC
@@ -43,12 +43,14 @@
<h2 ng-show="pkg.versions[0].summary" class="lead">{{pkg.versions[0].summary}}</h2> <p ng-show="pkg.versions[0].description">{{pkg.versions[0].description}}</p>

-            <h2>Actions</h2>
-
             <ul>
<li><a target="_blank" ng-show="homePageLink()" href="{{homePageLink()}}">Visit web site</a></li> - <li ng-show="canRemoveIcon()"><a href="" ng-click="goRemoveIcon()">Remove icon</a></li> - <li ng-show="canEditIcon()"><a href="" ng-click="goEditIcon()">Edit icons</a></li> + <li ng-show="canRemoveIcon()" pkg="pkg" show-if-pkg-permission="'PKG_EDITICON'">
+                    <a href="" ng-click="goRemoveIcon()">Remove icon</a>
+                </li>
+ <li ng-show="canEditIcon()" pkg="pkg" show-if-pkg-permission="'PKG_EDITICON'">
+                    <a href="" ng-click="goEditIcon()">Edit icons</a>
+                </li>
             </ul>
         </div>

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Sat Feb 8 09:19:06 2014 UTC
@@ -22,11 +22,11 @@
             }

             $scope.canRemoveIcon = function() {
- return $scope.pkg && $scope.pkg.canEdit && $scope.pkg.hasIcon;
+                return $scope.pkg && $scope.pkg.hasIcon;
             }

             $scope.canEditIcon = function() {
-                return $scope.pkg && $scope.pkg.canEdit;
+                return $scope.pkg;
             }

             $scope.homePageLink = function() {
@@ -75,7 +75,7 @@
             }

             $scope.goEditIcon = function() {
-                $location.path("/editpkgicon/"+$scope.pkg.name).search({});
+ $location.path("/editpkgicon/"+$scope.pkg.name).search({'arch':$routeParams.architectureCode});
             }

             $scope.goRemoveIcon = function() {
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicatordirective.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicatordirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -11,7 +11,7 @@
 angular.module('haikudepotserver').directive('activeIndicator',function() {
     return {
         restrict: 'E',
-        template:'<div ng-class="classes">&nbsp;</div>',
+        templateUrl:'/js/app/directive/activeindicator.html',
         replace: true,
         scope: {
             state: '='
@@ -25,7 +25,7 @@
                     $scope.$watch('state',function(newValue, oldValue) {
                         $scope.classes = [
                             'active-indicator',
- newValue ? 'active-indicator-true' : 'inactive-indicator-false' + newValue ? 'active-indicator-true' : 'active-indicator-false'
                         ];
                     });

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sun Feb 2 09:38:27 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -10,14 +10,18 @@
 angular.module('haikudepotserver').directive('repositoryLabel',function() {
     return {
         restrict: 'E',
-        template:'<span>{{repository.code}}</span>',
+ template:'<a href=\"\" ng-click=\"goView()\">{{repository.code}}</a>',
         replace: true,
         scope: {
             repository: '='
         },
         controller:
-            ['$scope',
-                function($scope) {
+            ['$scope','$location',
+                function($scope,$location) {
+
+                    $scope.goView = function() {
+ $location.path('/viewrepository/'+$scope.repository.code).search({});
+                    }
                 }
             ]
     };
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sat Feb 8 09:19:06 2014 UTC
@@ -8,6 +8,7 @@
         '$routeProvider',
         function($routeProvider) {
             $routeProvider
+ .when('/viewrepository/:code',{controller:'ViewRepositoryController', templateUrl:'/js/app/controller/viewrepository.html'}) .when('/listrepositories',{controller:'ListRepositoriesController', templateUrl:'/js/app/controller/listrepositories.html'}) .when('/runtimeinformation',{controller:'RuntimeInformationController', templateUrl:'/js/app/controller/runtimeinformation.html'}) .when('/more',{controller:'MoreController', templateUrl:'/js/app/controller/more.html'})
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sat Feb 8 09:19:06 2014 UTC
@@ -13,6 +13,13 @@

             var BreadcrumbsService = {

+                createViewRepository : function(repository) {
+                  return {
+                      title : repository.code,
+                      path : '/viewrepository/' + repository.code
+                  }
+                },
+
                 createListRepositories : function() {
                     return {
                         title : 'List Repositories',
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sun Feb 2 09:38:27 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sat Feb 8 09:19:06 2014 UTC
@@ -13,21 +13,26 @@

             var ErrorHandlingService = {

-                /**
- * <p>When a JSON-RPC failure occurs, this method can be invoked to provide uniform logging and
-                 * handling.</p>
-                 */
+                logJsonRpcError : function(jsonRpcErrorEnvelope, message) {
+ var prefix = message ? message + ' - json-rpc error; ' : 'json-rpc error; ';

-                handleJsonRpcError : function(jsonRpcErrorEnvelope) {
                     if(null==jsonRpcErrorEnvelope) {
- $log.error('json-rpc error; cause is unknown as no error envelope was available'); + $log.error(prefix+'cause is unknown as no error envelope was available');
                     }
                     else {
var code = jsonRpcErrorEnvelope.code ? jsonRpcErrorEnvelope.code : '?'; var message = jsonRpcErrorEnvelope.message ? jsonRpcErrorEnvelope.message : '?'; - $log.error('json-rpc error; code:'+code+", msg:"+message);
+                        $log.error(prefix+'code:'+code+", msg:"+message);
                     }
+                },
+
+                /**
+ * <p>When a JSON-RPC failure occurs, this method can be invoked to provide uniform logging and
+                 * handling.</p>
+                 */

+                handleJsonRpcError : function(jsonRpcErrorEnvelope) {
+ ErrorHandlingService.logJsonRpcError(jsonRpcErrorEnvelope);
                     $location.path("/error").search({});
                 },

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Sat Feb 1 07:54:02 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Sat Feb 8 09:19:06 2014 UTC
@@ -7,14 +7,185 @@
* <p>This service is here to maintain the current user's state. When the user logs in for example, this is stored * here. This service may take other actions such as configuring headers in the jsonRpc service when the user logs-in
  * or logs-out.</p>
+ *
+ * <p>This service also manages authorization information. There is the possibility to "check" on a permission.</p>
  */

 angular.module('haikudepotserver').factory('userState',
     [
-        '$log','$q','$rootScope','jsonRpc','pkgIcon',
-        function($log,$q,$rootScope,jsonRpc,pkgIcon) {
+        
'$log','$q','$rootScope','jsonRpc','pkgIcon','errorHandling','constants',
+ function($log,$q,$rootScope,jsonRpc,pkgIcon,errorHandling,constants) {
+
+            const SIZE_CHECKED_PERMISSION_CACHE = 25;

             var user = undefined;
+            var checkedPermissionCache = [];
+            var checkQueue = [];
+
+            function validateTargetAndPermissions(targetAndPermissions) {
+ _.each(targetAndPermissions, function(targetAndPermission) { + if(undefined === targetAndPermission.targetType | | !_.contains(['PKG','USER','REPOSITORY',null],targetAndPermission.targetType)) {
+                        throw 'illegal argument; bad targetType supplied';
+                    }
+
+ if(undefined === targetAndPermission.targetIdentifier) { + throw 'illegal argument; bad targetIdentifier supplied';
+                    }
+
+                    if(!targetAndPermission.permissionCode) {
+                        throw 'illegal argument; bad permission code';
+                    }
+                })
+            }
+
+            function resetAuthorization() {
+                checkedPermissionCache = [];
+                checkQueue = [];
+            }
+
+            function check(targetAndPermissions) {
+
+ // this function will see if it is able to resolve the top-most item in the queue by just looking + // in the cache. This function will return the result if there is one from cache; otherwise it
+                // will return null.
+
+ function tryDeriveFromCache(targetAndPermissionsToCheckAgainstCache) {
+
+                    if(!targetAndPermissionsToCheckAgainstCache.length) {
+                        return null;
+                    }
+
+                    if(!checkedPermissionCache.length) {
+                        return null;
+                    }
+
+                    var result = [];
+
+ for(var i=0;i<targetAndPermissionsToCheckAgainstCache.length;i++) {
+                        var cachedTargetAndPermission = _.findWhere(
+                            checkedPermissionCache,
+                            targetAndPermissionsToCheckAgainstCache[i]);
+
+                        if(!cachedTargetAndPermission) {
+                            return null;
+                        }
+                        else {
+                            result.push(cachedTargetAndPermission);
+                        }
+                    }
+
+                    return result;
+                }
+
+ // this function will take the item from the queue and handle it either by looking in the + // local cache or by talking to the remote application server.
+
+                function handleNextInCheckQueue() {
+                    if(checkQueue.length) {
+                        var request = checkQueue[0];
+ var result = tryDeriveFromCache(request.targetAndPermissions);
+
+                        if(null!=result) {
+                            request.deferred.resolve(result);
+                            checkQueue.shift();
+                            handleNextInCheckQueue();
+                        }
+                        else {
+
+ // we will have to go off to the application server to get the authorizations that we need
+
+                            var uncachedTargetAndPermissions = _.filter(
+                                request.targetAndPermissions,
+                                function(targetAndPermission) {
+ return !_.findWhere(checkedPermissionCache, targetAndPermission);
+                                }
+                            );
+
+                            // TODO - might be faster?
+ // if we have not exceeded the cache size, it would make sense to blend in a few more + // up-coming requests' data at the same time so that they will also be cached too. We + // might be able to be a bit smarter about this in the future.
+
+                            if(!uncachedTargetAndPermissions.length) {
+ throw 'illegal state; top-most request has no uncached target and permissions';
+                            }
+
+                            jsonRpc.call(
+ constants.ENDPOINT_API_V1_MISCELLANEOUS,
+                                    'checkAuthorization',
+ [{ targetAndPermissions : uncachedTargetAndPermissions }]
+                                ).then(
+                                function(data) {
+
+ // blend the new material into the cache.
+
+ checkedPermissionCache = data.targetAndPermissions.concat(checkedPermissionCache);
+
+ // we should now be in a position to resolve the permission from the cache.
+
+ result = tryDeriveFromCache(request.targetAndPermissions);
+
+                                    if(!result) {
+ throw 'illegal state; was not able to resolve the request from cache after fetching from application server';
+                                    }
+
+                                    request.deferred.resolve(result);
+
+ // now cull the cache so that we're not storing too much material. This is very
+                                    // simplistic; no LRU or anything.
+
+ if(checkedPermissionCache.length > SIZE_CHECKED_PERMISSION_CACHE) {
+                                        checkedPermissionCache.splice(
+                                            SIZE_CHECKED_PERMISSION_CACHE,
+ checkedPermissionCache.length-SIZE_CHECKED_PERMISSION_CACHE);
+                                    }
+
+ // drop this request now that it has been dealt with and move onto checking the
+                                    // next one.
+
+                                    checkQueue.shift();
+                                    handleNextInCheckQueue();
+                                },
+                                function(err) {
+
+ // if there is a problem then treat it as 'fatal'
+
+ $log.error('a problem has arisen checking the authorization');
+                                    errorHandling.handleJsonRpcError(err);
+                                    request.deferred.reject();
+                                    resetAuthorization();
+                                }
+                            );
+
+                        }
+                    }
+                }
+
+                var deferred = $q.defer();
+
+                // try handle this from the cached data.
+
+ var resultFromCache = tryDeriveFromCache(targetAndPermissions);
+
+                if(resultFromCache) {
+                    deferred.resolve(resultFromCache);
+                }
+                else {
+
+                    // push this request to the queue.
+
+                    checkQueue.push( {
+                        deferred : deferred,
+                        targetAndPermissions : targetAndPermissions
+                    });
+
+                    if(1==checkQueue.length) {
+                        handleNextInCheckQueue();
+                    }
+                }
+
+                return deferred.promise;
+            }

             var UserState = {

@@ -29,6 +200,8 @@

                         $rootScope.$broadcast('userChangeStart',value);

+                        resetAuthorization();
+
                         if(null==value) {
                             user = undefined;

@@ -55,6 +228,7 @@
                                 'Basic 
'+window.btoa(''+value.nickname+':'+value.passwordClear));

                             user = value;
+
                             $log.info('have set user; '+user.nickname);
                         }

@@ -62,7 +236,52 @@
                     }

                     return user;
+                },
+
+                // ---------------------
+                // AUTHORIZATION
+
+                /**
+ * <p>This function will check to make sure that the target and permissions supplied are authorized. + * The single argument should be an array of objects. Each object should have the following
+                 * elements;</p>
+                 *
+                 * <ul>
+                 *     <li>targetType</li>
+                 *     <li>targetIdentifier</li>
+                 *     <li>permissionCode</li>
+                 * </ul>
+                 *
+ * <p>Returned is a promise which resolves to a list of the requested permissions with an additional + * property "authorized" which can be either true or false.</p>
+                 */
+
+                authorize : function(targetAndPermissions) {
+                    validateTargetAndPermissions(targetAndPermissions);
+                    return check(targetAndPermissions);
+                },
+
+                areAuthorized : function(targetAndPermissions) {
+                    validateTargetAndPermissions(targetAndPermissions);
+
+                    var deferred = $q.defer();
+
+                    check(targetAndPermissions).then(
+                        function(data) {
+ // now filter through and make sure everything is true.
+                            deferred.resolve(!_.find(data, function(item) {
+                                return !item.authorized;
+                            }));
+                        },
+                        function() {
+ // error handling should already have been dealt with.
+                            deferred.reject();
+                        }
+                    );
+
+                    return deferred.promise;
                 }
+

             };

=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/MiscelaneousApiIT.java Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/MiscelaneousApiIT.java Sat Feb 8 09:19:06 2014 UTC
@@ -8,13 +8,15 @@
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import org.fest.assertions.Assertions;
 import org.haikuos.haikudepotserver.api1.MiscellaneousApi;
-import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesRequest; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesResult; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesRequest; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesResult;
+import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType;
+import org.haikuos.haikudepotserver.security.model.Permission;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.*;
+import org.haikuos.haikudepotserver.support.RuntimeInformationService;
 import org.haikuos.haikudepotsever.api1.support.AbstractIntegrationTest;
+import org.haikuos.haikudepotsever.api1.support.IntegrationTestSupportService;
 import org.junit.Test;

 import javax.annotation.Resource;
@@ -23,6 +25,90 @@

     @Resource
     MiscellaneousApi miscellaneousApi;
+
+    @Resource
+    RuntimeInformationService runtimeInformationService;
+
+    @Resource
+    IntegrationTestSupportService integrationTestSupportService;
+
+    private void assertTargetAndPermission(
+            IntegrationTestSupportService.StandardTestData data,
+ CheckAuthorizationResult.AuthorizationTargetAndPermission targetAndPermission,
+            boolean result) {
+ Assertions.assertThat(targetAndPermission.permissionCode).isEqualTo(Permission.PKG_EDITICON.name()); + Assertions.assertThat(targetAndPermission.targetIdentifier).isEqualTo(data.pkg1.getName()); + Assertions.assertThat(targetAndPermission.targetType).isEqualTo(AuthorizationTargetType.PKG); + Assertions.assertThat(targetAndPermission.authorized).isEqualTo(result);
+    }
+
+    @Test
+    public void checkAuthorizationRequest_asUnauthenticated() {
+ IntegrationTestSupportService.StandardTestData data = integrationTestSupportService.createStandardTestData();
+
+ CheckAuthorizationRequest request = new CheckAuthorizationRequest();
+        request.targetAndPermissions = Lists.newArrayList();
+
+ request.targetAndPermissions.add(new CheckAuthorizationRequest.AuthorizationTargetAndPermission(
+                AuthorizationTargetType.PKG,
+                data.pkg1.getName(),
+                Permission.PKG_EDITICON.name()));
+
+        // ------------------------------------
+ CheckAuthorizationResult result = miscellaneousApi.checkAuthorization(request);
+        // ------------------------------------
+
+ Assertions.assertThat(result.targetAndPermissions.size()).isEqualTo(1); + assertTargetAndPermission(data, result.targetAndPermissions.get(0), false);
+
+    }
+
+ // TODO : when some more sophisticated cases are available; implement some better tests
+    @Test
+    public void checkAuthorizationRequest_asRoot() {
+ IntegrationTestSupportService.StandardTestData data = integrationTestSupportService.createStandardTestData();
+
+        setAuthenticatedUserToRoot();
+
+ CheckAuthorizationRequest request = new CheckAuthorizationRequest();
+        request.targetAndPermissions = Lists.newArrayList();
+
+ request.targetAndPermissions.add(new CheckAuthorizationRequest.AuthorizationTargetAndPermission(
+                AuthorizationTargetType.PKG,
+                data.pkg1.getName(),
+                Permission.PKG_EDITICON.name()));
+
+        // ------------------------------------
+ CheckAuthorizationResult result = miscellaneousApi.checkAuthorization(request);
+        // ------------------------------------
+
+ Assertions.assertThat(result.targetAndPermissions.size()).isEqualTo(1); + assertTargetAndPermission(data, result.targetAndPermissions.get(0), true);
+
+    }
+
+    @Test
+    public void getRuntimeInformation_asUnauthenticated() {
+
+        // ------------------------------------
+ GetRuntimeInformationResult result = miscellaneousApi.getRuntimeInformation(new GetRuntimeInformationRequest());
+        // ------------------------------------
+
+        Assertions.assertThat(result.javaVersion).isNull();
+    }
+
+    @Test
+    public void getRuntimeInformation_asRoot() {
+
+        setAuthenticatedUserToRoot();
+
+        // ------------------------------------
+ GetRuntimeInformationResult result = miscellaneousApi.getRuntimeInformation(new GetRuntimeInformationRequest());
+        // ------------------------------------
+
+ Assertions.assertThat(result.javaVersion).isEqualTo(runtimeInformationService.getJavaVersion());
+
+    }

     @Test
     public void testGetAllMessages() {
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java Sat Feb 8 09:19:06 2014 UTC
@@ -69,7 +69,6 @@

         Assertions.assertThat(result.hasIcon).isFalse();
         Assertions.assertThat(result.name).isEqualTo("pkg1");
-        Assertions.assertThat(result.canEdit).isFalse();
         Assertions.assertThat(result.versions.size()).isEqualTo(1);
Assertions.assertThat(result.versions.get(0).architectureCode).isEqualTo("x86");
         Assertions.assertThat(result.versions.get(0).major).isEqualTo("1");
=======================================
***Additional files exist in this changeset.***

==============================================================================
Revision: d02f16acd3fc
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  8 09:30:39 2014 UTC
Log:      + add gui for triggering repository imports

http://code.google.com/p/haiku-depot-web-app/source/detail?r=d02f16acd3fc

Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js

=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sat Feb 8 09:30:39 2014 UTC
@@ -37,6 +37,6 @@
      * <p>This method will trigger the import process for a repository.</p>
      */

- TriggerImportRepositoryResult triggerImportRepositoryResult(TriggerImportRepositoryRequest triggerImportRepositoryRequest) throws ObjectNotFoundException; + TriggerImportRepositoryResult triggerImportRepository(TriggerImportRepositoryRequest triggerImportRepositoryRequest) throws ObjectNotFoundException;

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sat Feb 8 09:30:39 2014 UTC
@@ -17,7 +17,6 @@
import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
 import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
 import org.haikuos.haikudepotserver.dataobjects.Repository;
-import org.haikuos.haikudepotserver.dataobjects.User;
 import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob;
 import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
 import org.haikuos.haikudepotserver.repository.RepositoryImportService;
@@ -52,7 +51,7 @@

     // note; no integration test for this one.
     @Override
-    public TriggerImportRepositoryResult triggerImportRepositoryResult(
+    public TriggerImportRepositoryResult triggerImportRepository(
             TriggerImportRepositoryRequest triggerImportRepositoryRequest)
             throws ObjectNotFoundException {

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sat Feb 8 09:30:39 2014 UTC
@@ -2,6 +2,11 @@

 <div class="content-container">

+    <div class="info-container" ng-show="didTriggerImportRepository">
+ <strong>Import triggered -</strong> the application will import the repository's data
+        soon.
+    </div>
+
     <dl>
         <dt>Code</dt>
         <dd><code>{{repository.code}}</code>&nbsp;</dd>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sat Feb 8 09:30:39 2014 UTC
@@ -6,14 +6,15 @@
 angular.module('haikudepotserver').controller(
     'ViewRepositoryController',
     [
-        '$scope','$log','$location','$routeParams',
+        '$scope','$log','$location','$routeParams','$timeout',
         'jsonRpc','constants','userState','errorHandling','breadcrumbs',
         function(
-            $scope,$log,$location,$routeParams,
+            $scope,$log,$location,$routeParams,$timeout,
             jsonRpc,constants,userState,errorHandling,breadcrumbs) {

             $scope.breadcrumbItems = undefined;
             $scope.repository = undefined;
+            $scope.didTriggerImportRepository = false;
             var amUpdatingActive = false;

             refetchRepository();
@@ -68,7 +69,23 @@
              */

             $scope.goTriggerImport = function() {
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_REPOSITORY,
+                        "triggerImportRepository",
+                        [{ code: $routeParams.code }]
+                    ).then(
+                    function(result) {
+ $log.info('triggered import for repository; '+$scope.repository.code);
+                        $scope.didTriggerImportRepository = true;
+                        $timeout(function() {
+                            $scope.didTriggerImportRepository = false;
+                        }, 3000)

+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
             }

             function refreshBreadcrumbItems() {

==============================================================================
Revision: b3fbcf57101d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  8 10:16:22 2014 UTC
Log:      + small changes from checks

http://code.google.com/p/haiku-depot-web-app/source/detail?r=b3fbcf57101d

Added:
/haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabel.html
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js

=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabel.html Sat Feb 8 10:16:22 2014 UTC
@@ -0,0 +1,1 @@
+<span><span ng-show="!canView">{{repository.code}}</span><a href="" ng-click="goView()" ng-show="canView">{{repository.code}}</a></span>
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java Sat Feb 8 10:16:22 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sat Feb 8 10:16:22 2014 UTC
@@ -10,7 +10,6 @@
 import com.google.common.base.Strings;
 import org.apache.cayenne.DataObject;
 import org.apache.cayenne.ObjectContext;
-import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType;
 import org.haikuos.haikudepotserver.dataobjects.Pkg;
 import org.haikuos.haikudepotserver.dataobjects.Repository;
 import org.haikuos.haikudepotserver.dataobjects.User;
@@ -26,20 +25,20 @@
 @Service
 public class AuthorizationService {

- private AuthorizationTargetType deriveTargetType(DataObject dataObject) {
+    private TargetType deriveTargetType(DataObject dataObject) {
         if(null==dataObject)
             return null;

         if(User.class.isAssignableFrom(dataObject.getClass())) {
-            return AuthorizationTargetType.USER;
+            return TargetType.USER;
         }

         if(Pkg.class.isAssignableFrom(dataObject.getClass())) {
-            return AuthorizationTargetType.PKG;
+            return TargetType.PKG;
         }

         if(Repository.class.isAssignableFrom(dataObject.getClass())) {
-            return AuthorizationTargetType.REPOSITORY;
+            return TargetType.REPOSITORY;
         }

throw new IllegalStateException("the data object type '"+dataObject.getClass().getSimpleName()+"' is not handled");
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sat Feb 8 10:16:22 2014 UTC
@@ -5,30 +5,28 @@

 package org.haikuos.haikudepotserver.security.model;

-import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType;
-
 public enum Permission {

-    REPOSITORY_VIEW(AuthorizationTargetType.REPOSITORY),
-    REPOSITORY_EDIT(AuthorizationTargetType.REPOSITORY),
-    REPOSITORY_IMPORT(AuthorizationTargetType.REPOSITORY),
+    REPOSITORY_VIEW(TargetType.REPOSITORY),
+    REPOSITORY_EDIT(TargetType.REPOSITORY),
+    REPOSITORY_IMPORT(TargetType.REPOSITORY),
     REPOSITORY_LIST(null),
     REPOSITORY_LIST_INACTIVE(null),

-    USER_VIEW(AuthorizationTargetType.USER),
-    USER_EDIT(AuthorizationTargetType.USER),
-    USER_CHANGEPASSWORD(AuthorizationTargetType.USER),
+    USER_VIEW(TargetType.USER),
+    USER_EDIT(TargetType.USER),
+    USER_CHANGEPASSWORD(TargetType.USER),
     USER_LIST(null),

-    PKG_EDITICON(AuthorizationTargetType.PKG);
+    PKG_EDITICON(TargetType.PKG);

-    private AuthorizationTargetType requiredTargetType;
+    private TargetType requiredTargetType;

-    Permission(AuthorizationTargetType requiredTargetType) {
+    Permission(TargetType requiredTargetType) {
         this.requiredTargetType = requiredTargetType;
     }

-    public AuthorizationTargetType getRequiredTargetType() {
+    public TargetType getRequiredTargetType() {
         return requiredTargetType;
     }

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sat Feb 8 10:16:22 2014 UTC
@@ -10,14 +10,31 @@
 angular.module('haikudepotserver').directive('repositoryLabel',function() {
     return {
         restrict: 'E',
- template:'<a href=\"\" ng-click=\"goView()\">{{repository.code}}</a>',
+        templateUrl:'/js/app/directive/repositorylabel.html',
         replace: true,
         scope: {
             repository: '='
         },
         controller:
-            ['$scope','$location',
-                function($scope,$location) {
+            ['$scope','$location','userState',
+                function($scope,$location,userState) {
+
+                    $scope.canView = false;
+
+ $scope.$watch('repository',function(newValue,oldValue) {
+                        if(!newValue) {
+                            $scope.canView = false;
+                        }
+                        else {
+                            userState.areAuthorized([{
+                                    targetType:'REPOSITORY',
+                                    targetIdentifier:newValue.code,
+                                    permissionCode:'REPOSITORY_VIEW'
+                                }]).then(function(flag) {
+                                $scope.canView = flag;
+                            });
+                        }
+                    })

                     $scope.goView = function() {
$location.path('/viewrepository/'+$scope.repository.code).search({});

==============================================================================
Revision: 11b560e3e392
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  8 11:31:22 2014 UTC
Log:      + better pagination action directives

http://code.google.com/p/haiku-depot-web-app/source/detail?r=11b560e3e392

Added:
/haikudepotserver-webapp/src/main/webapp/js/app/directive/paginationarrowdirective.js
Modified:
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
 /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html

=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/paginationarrowdirective.js Sat Feb 8 11:31:22 2014 UTC
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This directive will render the "page left" and "page right" arrows in a table of data.</p>
+ */
+
+angular.module('haikudepotserver').directive('paginationArrow',function() {
+    return {
+        restrict: 'E',
+        link : function($scope,element,attributes) {
+
+            var direction = attributes['direction'];
+            var onPageExpression = attributes['pageClick'];
+            var activeExpression = attributes['active'];
+
+            var active = false;
+            var svgElement;
+            var svg;
+
+            switch(direction) {
+                case 'right':
+ svg = '<svg height=\"12\" width=\"12\"><path fill=\"black\" fill-opacity=\"0.5\" d=\"M0 4.5 L0 7.5 L4 7.5 L4 12 L12 6 L4 0 L4 4.5\"/></svg>';
+                    break;
+
+                case 'left':
+ svg = '<svg height=\"12\" width=\"12\"><path fill=\"black\" fill-opacity=\"0.5\" d=\"M12 4.5 L12 7.5 L8 7.5 L8 12 L0 6 L8 0 L8 4.5\"/></svg>';
+                    break;
+
+                default:
+ throw 'illegal direction on pagination arrow; '+direction;
+            }
+
+            // replaces the element supplied with the SVG one.
+            element.after(svg);
+            svgElement = element.next();
+            element.remove();
+
+            svgElement.on('click',function() {
+                if(active && onPageExpression) {
+                    $scope.$apply(onPageExpression);
+                }
+            })
+
+            $scope.$watch(activeExpression,function(newValue,oldValue) {
+                if(newValue) {
+                    svgElement.children().attr('fill-opacity','1.0');
+                }
+                else {
+                    svgElement.children().attr('fill-opacity','0.5');
+                }
+
+                active = newValue;
+            });
+
+        }
+    };
+});
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Feb 8 11:31:22 2014 UTC
@@ -62,6 +62,7 @@
<value>/js/app/directive/showifpkgpermissiondirective.js</value> <value>/js/app/directive/showifrepositorypermissiondirective.js</value> <value>/js/app/directive/showifpermissiondirective.js</value> + <value>/js/app/directive/paginationarrowdirective.js</value>

<value>/js/app/controller/viewpkgcontroller.js</value> <value>/js/app/controller/homecontroller.js</value>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sat Feb 8 11:31:22 2014 UTC
@@ -232,8 +232,8 @@
 }

 .table-general-pagination-container {
-    padding-top:2px;
-    padding-bottom:2px;
+    padding-top:3px;
+    padding-bottom:0px;
     padding-left:4px;
     padding-right:4px;
position: absolute; /* assumes that this is inside a 'table-general-container' */
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Sat Feb 8 11:31:22 2014 UTC
@@ -35,10 +35,8 @@
         <div ng-show="pkgs && pkgs.length" class="table-general-container">

             <div class="table-general-pagination-container">
- <a href="" ng-click="goPreviousPage()" ng-show="0!=offset">&laquo;</a>
-                <span ng-show="0==offset">&laquo;</span>
- <a href="" ng-click="goNextPage()" ng-show="hasMore">&raquo;</a>
-                <span ng-show="!hasMore">&raquo;</span>
+ <pagination-arrow active="0!=offset" page-click="goPreviousPage()" direction="left"></pagination-arrow> + <pagination-arrow active="hasMore" page-click="goNextPage()" direction="right"></pagination-arrow>
             </div>

             <table class="table-general">
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sat Feb 8 11:31:22 2014 UTC
@@ -24,10 +24,8 @@
<div ng-show="repositories && repositories.length" class="table-general-container">

             <div class="table-general-pagination-container">
- <a href="" ng-click="goPreviousPage()" ng-show="0!=offset">&laquo;</a>
-                <span ng-show="0==offset">&laquo;</span>
- <a href="" ng-click="goNextPage()" ng-show="hasMore">&raquo;</a>
-                <span ng-show="!hasMore">&raquo;</span>
+ <pagination-arrow active="0!=offset" page-click="goPreviousPage()" direction="left"></pagination-arrow> + <pagination-arrow active="hasMore" page-click="goNextPage()" direction="right"></pagination-arrow>
             </div>

             <table class="table-general">
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Sat Feb 8 11:31:22 2014 UTC
@@ -26,8 +26,8 @@
         <div id="pkg-screenshot-container">

             <div id="pkg-screenshot-pagination-container">
-                <a href="">&laquo;</a>
-                <a href="">&raquo;</a>
+ <pagination-arrow active="false" direction="left"></pagination-arrow> + <pagination-arrow active="false" direction="right"></pagination-arrow>
             </div>

             <div id="pkg-screenshot-title">

==============================================================================
Revision: b001fd64677c
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  9 06:49:48 2014 UTC
Log:      + create repository api
+ changes to update repository to support url changes

http://code.google.com/p/haiku-depot-web-app/source/detail?r=b001fd64677c

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/CreateRepositoryRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/CreateRepositoryResult.java
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/CreateRepositoryRequest.java Sun Feb 9 06:49:48 2014 UTC
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+public class CreateRepositoryRequest {
+
+    public String code;
+    public String architectureCode;
+    public String url;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/CreateRepositoryResult.java Sun Feb 9 06:49:48 2014 UTC
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+public class CreateRepositoryResult {
+
+}
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sat Feb 8 09:30:39 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sun Feb 9 06:49:48 2014 UTC
@@ -39,4 +39,12 @@

TriggerImportRepositoryResult triggerImportRepository(TriggerImportRepositoryRequest triggerImportRepositoryRequest) throws ObjectNotFoundException;

+    /**
+     * <p>This method will create a repository.  This method will throw
+ * {@link org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException} if the architecture identified by a
+     * supplied code is not able to be found as an architecture.</p>
+     */
+
+ CreateRepositoryResult createRepository(CreateRepositoryRequest createRepositoryRequest) throws ObjectNotFoundException;
+
 }
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java Sun Feb 9 06:49:48 2014 UTC
@@ -10,13 +10,16 @@
 public class UpdateRepositoryRequest {

     public enum Filter {
-        ACTIVE
+        ACTIVE,
+        URL
     };

     public String code;

     public Boolean active;

+    public String url;
+
     public List<Filter> filter;

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sat Feb 8 09:30:39 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sun Feb 9 06:49:48 2014 UTC
@@ -16,6 +16,9 @@
 import org.haikuos.haikudepotserver.api1.model.repository.*;
import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
 import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+import org.haikuos.haikudepotserver.api1.support.ValidationException;
+import org.haikuos.haikudepotserver.api1.support.ValidationFailure;
+import org.haikuos.haikudepotserver.dataobjects.Architecture;
 import org.haikuos.haikudepotserver.dataobjects.Repository;
 import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob;
 import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
@@ -214,6 +217,11 @@

                     break;

+                case URL:
+ repositoryOptional.get().setUrl(updateRepositoryRequest.url); + logger.info("did set the url on repository {} to {}", updateRepositoryRequest.code,updateRepositoryRequest.url);
+                    break;
+
                 default:
throw new IllegalStateException("unhandled filter for updating a repository");
             }
@@ -228,5 +236,55 @@

         return new UpdateRepositoryResult();
     }
+
+    @Override
+    public CreateRepositoryResult createRepository(
+            CreateRepositoryRequest createRepositoryRequest)
+            throws ObjectNotFoundException {
+
+        Preconditions.checkNotNull(createRepositoryRequest);
+
+        final ObjectContext context = serverRuntime.getContext();
+
+        authorizationService.check(
+                context,
+                tryObtainAuthenticatedUser(context).orNull(),
+                null,
+                Permission.REPOSITORY_CREATE);
+
+ Optional<Architecture> architectureOptional = Architecture.getByCode(context, createRepositoryRequest.architectureCode);
+
+        if(!architectureOptional.isPresent()) {
+ throw new ObjectNotFoundException(Architecture.class.getSimpleName(), createRepositoryRequest.architectureCode);
+        }
+
+        // the code must be supplied.
+
+        if(Strings.isNullOrEmpty(createRepositoryRequest.code)) {
+ throw new ValidationException(new ValidationFailure(Repository.CODE_PROPERTY, "required"));
+        }
+
+ // check to see if there is an existing repository with the same code; non-unique.
+
+        {
+ Optional<Repository> repositoryOptional = Repository.getByCode(context, createRepositoryRequest.code);
+
+            if(repositoryOptional.isPresent()) {
+ throw new ValidationException(new ValidationFailure(Repository.CODE_PROPERTY, "unique"));
+            }
+        }
+
+        Repository repository = context.newObject(Repository.class);
+
+        repository.setCode(createRepositoryRequest.code);
+        repository.setActive(Boolean.TRUE);
+        repository.setUrl(createRepositoryRequest.url);
+        repository.setArchitecture(architectureOptional.get());
+
+        context.commitChanges();
+
+        return new CreateRepositoryResult();
+    }
+

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sat Feb 8 10:16:22 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sun Feb 9 06:49:48 2014 UTC
@@ -111,6 +111,13 @@
         Preconditions.checkNotNull(permission);
         Preconditions.checkNotNull(objectContext);
Preconditions.checkState(deriveTargetType(target) == permission.getRequiredTargetType());
+
+ // if the authenticated user is not active then there should not be a situation arising where
+        // an authorization check is being made.
+
+        if(null!=authenticatedUser && !authenticatedUser.getActive()) {
+ throw new IllegalStateException("the authenticated user '"+authenticatedUser.getNickname()+"' is not active and so authorization queries cannot be resolved for them");
+        }

         switch(permission) {

@@ -128,6 +135,9 @@
             case REPOSITORY_LIST_INACTIVE:
return null!=authenticatedUser && authenticatedUser.getIsRoot();

+            case REPOSITORY_CREATE:
+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+
             case USER_VIEW:
             case USER_EDIT:
             case USER_CHANGEPASSWORD:
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sat Feb 8 10:16:22 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sun Feb 9 06:49:48 2014 UTC
@@ -12,6 +12,7 @@
     REPOSITORY_IMPORT(TargetType.REPOSITORY),
     REPOSITORY_LIST(null),
     REPOSITORY_LIST_INACTIVE(null),
+    REPOSITORY_CREATE(null),

     USER_VIEW(TargetType.USER),
     USER_EDIT(TargetType.USER),
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java Sun Feb 9 06:49:48 2014 UTC
@@ -6,12 +6,14 @@
 package org.haikuos.haikudepotsever.api1;

 import com.google.common.base.Optional;
+import junit.framework.Assert;
 import org.apache.cayenne.ObjectContext;
 import org.fest.assertions.Assertions;
 import org.haikuos.haikudepotserver.api1.RepositoryApi;
 import org.haikuos.haikudepotserver.api1.model.pkg.SearchPkgsRequest;
 import org.haikuos.haikudepotserver.api1.model.repository.*;
 import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+import org.haikuos.haikudepotserver.api1.support.ValidationException;
 import org.haikuos.haikudepotserver.dataobjects.Repository;
 import org.haikuos.haikudepotsever.api1.support.AbstractIntegrationTest;
import org.haikuos.haikudepotsever.api1.support.IntegrationTestSupportService;
@@ -83,5 +85,50 @@
         Assertions.assertThat(result.architectureCode).isEqualTo("x86");
         Assertions.assertThat(result.url).isEqualTo("file:///");
     }
+
+    @Test
+    public void testCreateRepository_ok() throws Exception {
+        setAuthenticatedUserToRoot();
+
+        CreateRepositoryRequest request = new CreateRepositoryRequest();
+        request.architectureCode = "x86";
+        request.code = "integrationtest";
+        request.url = "http://www.somewhere.co.nz";;
+
+        // ------------------------------------
+        repositoryApi.createRepository(request);
+        // ------------------------------------
+
+        ObjectContext context = serverRuntime.getContext();
+ Optional<Repository> repositoryAfterOptional = Repository.getByCode(context,"integrationtest"); + Assertions.assertThat(repositoryAfterOptional.get().getActive()).isTrue(); + Assertions.assertThat(repositoryAfterOptional.get().getArchitecture().getCode()).isEqualTo("x86"); + Assertions.assertThat(repositoryAfterOptional.get().getCode()).isEqualTo("integrationtest"); + Assertions.assertThat(repositoryAfterOptional.get().getUrl()).isEqualTo("http://www.somewhere.co.nz";);
+    }
+
+    @Test
+    public void testCreateRepository_codeNotUnique() throws Exception {
+ IntegrationTestSupportService.StandardTestData data = integrationTestSupportService.createStandardTestData();
+        setAuthenticatedUserToRoot();
+
+        CreateRepositoryRequest request = new CreateRepositoryRequest();
+        request.architectureCode = "x86";
+        request.code = data.repository.getCode();
+        request.url = "http://www.somewhere.co.nz";;
+
+        try {
+            // ------------------------------------
+            repositoryApi.createRepository(request);
+            // ------------------------------------
+
+ Assert.fail("the repository should not have been able to be created against an already existing repository code");
+        }
+        catch(ValidationException ve) {
+ Assertions.assertThat(ve.getValidationFailures().size()).isEqualTo(1); + Assertions.assertThat(ve.getValidationFailures().get(0).getMessage()).isEqualTo("unique"); + Assertions.assertThat(ve.getValidationFailures().get(0).getProperty()).isEqualTo(Repository.CODE_PROPERTY);
+        }
+    }

 }

==============================================================================
Revision: 04264ff1b771
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  9 10:00:28 2014 UTC
Log:      + add and edit repository functionality

http://code.google.com/p/haiku-depot-web-app/source/detail?r=04264ff1b771

Added:
/haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js
Modified:
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersion.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersionUrl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/support/AbstractDataObject.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java
 /haikudepotserver-webapp/src/main/resources/messages.properties
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
/haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/routes.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js

=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html Sun Feb 9 10:00:28 2014 UTC
@@ -0,0 +1,63 @@
+<breadcrumbs items="breadcrumbItems"></breadcrumbs>
+
+<div class="content-container">
+    <form name="addEditRepositoryForm" novalidate="novalidate">
+
+        <label for="code">Code</label>
+ <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('code')">
+            <div ng-show="amEditing" class="form-control-group-static">
+                <code>{{workingRepository.code}}</code>
+            </div>
+            <input
+                    id="code"
+                    type="text"
+                    name="code"
+                    ng-show="!amEditing"
+                    ng-change="codeChanged()"
+                    ng-required="true"
+                    ng-pattern="/^[a-z0-9]{2,16}$/"
+                    ng-model="workingRepository.code"></input>
+ <error-messages key-prefix="addEditRepository.code" error="addEditRepositoryForm.code.$error"></error-messages>
+        </div>
+
+        <label for="code">Architecture</label>
+        <div class="form-control-group">
+            <div ng-show="amEditing" class="form-control-group-static">
+                <code>{{workingRepository.architecture.code}}</code>
+            </div>
+            <select
+                    ng-show="!amEditing"
+                    ng-model="workingRepository.architecture"
+ ng-options="anArchitecture.code for anArchitecture in architectures">
+            </select>
+        </div>
+
+        <label for="url">URL</label>
+ <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('url')">
+            <input
+                    id="url"
+                    type="url"
+                    size="64"
+                    name="url"
+                    ng-required="true"
+                    ng-model="workingRepository.url"></input>
+ <error-messages key-prefix="addEditRepository.url" error="addEditRepositoryForm.url.$error"></error-messages>
+        </div>
+
+        <div class="form-action-container">
+            <button
+                    ng-disabled="addEditRepositoryForm.$invalid"
+                    ng-click="goSave()"
+                    type="submit"
+                    class="main-action">
+                <span ng-show="amEditing">Save Changes</span>
+                <span ng-show="!amEditing">Add</span>
+            </button>
+        </div>
+
+    </form>
+</div>
+
+<div class="footer"></div>
+<spinner spin="shouldSpin()"></spinner>
+
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js Sun Feb 9 10:00:28 2014 UTC
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'AddEditRepositoryController',
+    [
+        '$scope','$log','$location','$routeParams',
+        
'jsonRpc','constants','breadcrumbs','userState','errorHandling','architectures',
+        function(
+            $scope,$log,$location,$routeParams,
+ jsonRpc,constants,breadcrumbs,userState,errorHandling,architectures) {
+
+            $scope.breadcrumbItems = undefined;
+            $scope.workingRepository = undefined;
+            $scope.architectures = architectures;
+            $scope.amEditing = !!$routeParams.code;
+            var amSaving = false;
+
+            $scope.shouldSpin = function() {
+                return undefined == $scope.workingRepository || amSaving;
+            }
+
+            $scope.deriveFormControlsContainerClasses = function(name) {
+ return $scope.addEditRepositoryForm[name].$invalid ? ['form-control-group-error'] : [];
+            }
+
+ // the validity of this field may have been set to false because of the + // attempt to save it on the server-side. This function will be hit when
+            // the code changes so that validation does not apply.
+
+            $scope.codeChanged = function() {
+ $scope.addEditRepositoryForm.code.$setValidity('unique',true);
+            }
+
+            refreshRepository();
+
+            function refreshBreadcrumbItems() {
+                var b = [
+                    breadcrumbs.createMore(),
+                    breadcrumbs.createListRepositories()
+                ];
+
+                if($scope.amEditing) {
+ b.push(breadcrumbs.createEditRepository($scope.workingRepository));
+                }
+                else {
+                    b.push(breadcrumbs.createAddRepository());
+                }
+
+                $scope.breadcrumbItems = b;
+            }
+
+            function refreshRepository() {
+                if($routeParams.code) {
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_REPOSITORY,
+                        "getRepository",
+                        [{ code : $routeParams.code }]
+                    ).then(
+                    function(result) {
+                        $scope.workingRepository = {
+                            code : result.code,
+                            url : result.url,
+ architecture : _.find(architectures, function(a) {
+                                return a.code == result.architectureCode;
+                            })
+                        }
+                        refreshBreadcrumbItems();
+                        $log.info('fetched repository; '+result.code);
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+                }
+                else {
+                    $scope.workingRepository = {
+                        architecture : architectures[0]
+                    };
+                    refreshBreadcrumbItems();
+                }
+            };
+
+            $scope.goSave = function() {
+
+                if($scope.addEditRepositoryForm.$invalid) {
+ throw 'expected the save of a repository to only to be possible if the form is valid';
+                }
+
+                amSaving = true;
+
+                if($scope.amEditing) {
+                    jsonRpc.call(
+                            constants.ENDPOINT_API_V1_REPOSITORY,
+                            "updateRepository",
+                            [{
+                                filter : [ 'URL' ],
+                                url : $scope.workingRepository.url,
+                                code : $scope.workingRepository.code
+                            }]
+                        ).then(
+                        function(result) {
+ $log.info('did update repository; '+$scope.workingRepository.code); + $location.path('/viewrepository/'+$scope.workingRepository.code).search({});
+                        },
+                        function(err) {
+
+                            switch(err.code) {
+                                case jsonRpc.errorCodes.VALIDATION:
+                                    errorHandling.handleValidationFailures(
+                                        err.data.validationfailures,
+                                        $scope.addEditRepositoryForm);
+                                    break;
+
+                                default:
+                                    errorHandling.handleJsonRpcError(err);
+                                    break;
+                            }
+
+                            amSaving = false;
+                        }
+                    );
+                }
+                else {
+                    jsonRpc.call(
+                            constants.ENDPOINT_API_V1_REPOSITORY,
+                            "createRepository",
+                            [{
+ architectureCode : $scope.workingRepository.architecture.code,
+                                url : $scope.workingRepository.url,
+                                code : $scope.workingRepository.code
+                            }]
+                        ).then(
+                        function(result) {
+ $log.info('did create repository; '+$scope.workingRepository.code); + $location.path('/viewrepository/'+$scope.workingRepository.code).search({});
+                        },
+                        function(err) {
+
+                            switch(err.code) {
+                                case jsonRpc.errorCodes.VALIDATION:
+                                    errorHandling.handleValidationFailures(
+                                        err.data.validationfailures,
+                                        $scope.addEditRepositoryForm);
+                                    break;
+
+                                default:
+                                    errorHandling.handleJsonRpcError(err);
+                                    break;
+                            }
+
+                            amSaving = false;
+                        }
+                    );
+                }
+
+            }
+
+        }
+    ]
+);
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sun Feb 9 06:49:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sun Feb 9 10:00:28 2014 UTC
@@ -32,7 +32,6 @@
 import org.springframework.stereotype.Component;

 import javax.annotation.Resource;
-import javax.naming.ldap.PagedResultsControl;
 import java.util.List;

 @Component
@@ -246,11 +245,13 @@

         final ObjectContext context = serverRuntime.getContext();

-        authorizationService.check(
+        if(!authorizationService.check(
                 context,
                 tryObtainAuthenticatedUser(context).orNull(),
                 null,
-                Permission.REPOSITORY_CREATE);
+                Permission.REPOSITORY_ADD)) {
+            throw new AuthorizationFailureException();
+        }

Optional<Architecture> architectureOptional = Architecture.getByCode(context, createRepositoryRequest.architectureCode);

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java Wed Nov 20 10:28:55 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -104,9 +104,10 @@
public Map<String,String> apply(org.apache.cayenne.validation.ValidationFailure input) { if(BeanValidationFailure.class.isAssignableFrom(input.getClass())) { BeanValidationFailure beanValidationFailure = (BeanValidationFailure) input; + Object err = beanValidationFailure.getError();
                                                 return ImmutableMap.of(
"property", beanValidationFailure.getProperty(), - "message", beanValidationFailure.toString()); + "message", null!=err ? err.toString() : "");
                                             }

if(SimpleValidationFailure.class.isAssignableFrom(input.getClass())) {
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java Sun Feb 9 10:00:28 2014 UTC
@@ -11,7 +11,7 @@
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.validation.SimpleValidationFailure;
+import org.apache.cayenne.validation.BeanValidationFailure;
 import org.apache.cayenne.validation.ValidationResult;
 import org.haikuos.haikudepotserver.dataobjects.auto._Pkg;
import org.haikuos.haikudepotserver.dataobjects.support.CreateAndModifyTimestamped;
@@ -38,7 +38,7 @@

         if(null != getName()) {
             if(!NAME_PATTERN.matcher(getName()).matches()) {
- validationResult.addFailure(new SimpleValidationFailure(this,NAME_PATTERN + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,NAME_PROPERTY,"malformed"));
             }
         }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersion.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersion.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -15,7 +15,7 @@
 import org.apache.cayenne.query.Ordering;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.query.SortOrder;
-import org.apache.cayenne.validation.SimpleValidationFailure;
+import org.apache.cayenne.validation.BeanValidationFailure;
 import org.apache.cayenne.validation.ValidationResult;
 import org.haikuos.haikudepotserver.dataobjects.auto._PkgVersion;
import org.haikuos.haikudepotserver.dataobjects.support.CreateAndModifyTimestamped;
@@ -78,31 +78,31 @@

         if(null != getMajor()) {
             if(!MAJOR_PATTERN.matcher(getMajor()).matches()) {
- validationResult.addFailure(new SimpleValidationFailure(this,MAJOR_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,MAJOR_PROPERTY,"malformed"));
             }
         }

         if(null != getMinor()) {
             if(!MINOR_PATTERN.matcher(getMinor()).matches()) {
- validationResult.addFailure(new SimpleValidationFailure(this,MINOR_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,MINOR_PROPERTY,"malformed"));
             }
         }

         if(null != getMicro()) {
             if(!MICRO_PATTERN.matcher(getMicro()).matches()) {
- validationResult.addFailure(new SimpleValidationFailure(this,MICRO_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,MICRO_PROPERTY,"malformed"));
             }
         }

         if(null != getPreRelease()) {
             if(!PRE_RELEASE_PATTERN.matcher(getPreRelease()).matches()) {
- validationResult.addFailure(new SimpleValidationFailure(this,PRE_RELEASE_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,PRE_RELEASE_PROPERTY,"malformed"));
             }
         }

         if(null != getRevision()) {
             if(getRevision().intValue() <= 0) {
- validationResult.addFailure(new SimpleValidationFailure(this,REVISION_PROPERTY + ".lessThanEqualZero")); + validationResult.addFailure(new BeanValidationFailure(this,REVISION_PROPERTY,"lessThanEqualZero"));
             }
         }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersionUrl.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersionUrl.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,11 +1,11 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

 package org.haikuos.haikudepotserver.dataobjects;

-import org.apache.cayenne.validation.SimpleValidationFailure;
+import org.apache.cayenne.validation.BeanValidationFailure;
 import org.apache.cayenne.validation.ValidationResult;
 import org.haikuos.haikudepotserver.dataobjects.auto._PkgVersionUrl;

@@ -23,7 +23,7 @@
                     new URL(getUrl());
             }
             catch(MalformedURLException mue) {
- validationResult.addFailure(new SimpleValidationFailure(this,URL_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,URL_PROPERTY,"malformed"));
             }
         }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -12,7 +12,7 @@
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.query.ObjectIdQuery;
 import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.validation.SimpleValidationFailure;
+import org.apache.cayenne.validation.BeanValidationFailure;
 import org.apache.cayenne.validation.ValidationResult;
 import org.haikuos.haikudepotserver.dataobjects.auto._Repository;
 import org.haikuos.haikudepotserver.dataobjects.support.Coded;
@@ -40,7 +40,7 @@

         if(null != getCode()) {
             if(!CODE_PATTERN.matcher(getCode()).matches()) {
- validationResult.addFailure(new SimpleValidationFailure(this,CODE_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,CODE_PROPERTY,"malformed"));
             }
         }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -11,7 +11,7 @@
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.validation.SimpleValidationFailure;
+import org.apache.cayenne.validation.BeanValidationFailure;
 import org.apache.cayenne.validation.ValidationResult;
 import org.haikuos.haikudepotserver.dataobjects.auto._User;

@@ -65,19 +65,19 @@

         if(null != getNickname()) {
             if(!NICKNAME_PATTERN.matcher(getNickname()).matches()) {
- validationResult.addFailure(new SimpleValidationFailure(this,NICKNAME_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,NICKNAME_PROPERTY,"malformed"));
             }
         }

         if(null != getPasswordHash()) {
if(!PASSWORDHASH_PATTERN.matcher(getPasswordHash()).matches()) { - validationResult.addFailure(new SimpleValidationFailure(this,PASSWORD_HASH_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,PASSWORD_HASH_PROPERTY,"malformed"));
             }
         }

         if(null != getPasswordSalt()) {
if(!PASSWORDSALT_PATTERN.matcher(getPasswordSalt()).matches()) { - validationResult.addFailure(new SimpleValidationFailure(this,PASSWORD_HASH_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,PASSWORD_HASH_PROPERTY,"malformed"));
             }
         }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/support/AbstractDataObject.java Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/support/AbstractDataObject.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,12 +1,12 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

 package org.haikuos.haikudepotserver.dataobjects.support;

 import org.apache.cayenne.CayenneDataObject;
-import org.apache.cayenne.validation.SimpleValidationFailure;
+import org.apache.cayenne.validation.BeanValidationFailure;
 import org.apache.cayenne.validation.ValidationResult;

 import java.util.regex.Pattern;
@@ -33,7 +33,7 @@

             if(null != coded.getCode()) {
                 if(!CODE_PATTERN.matcher(coded.getCode()).matches()) {
- validationResult.addFailure(new SimpleValidationFailure(this,"code.malformed")); + validationResult.addFailure(new BeanValidationFailure(this,"code","malformed"));
                 }
             }
         }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sun Feb 9 06:49:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sun Feb 9 10:00:28 2014 UTC
@@ -135,7 +135,7 @@
             case REPOSITORY_LIST_INACTIVE:
return null!=authenticatedUser && authenticatedUser.getIsRoot();

-            case REPOSITORY_CREATE:
+            case REPOSITORY_ADD:
return null!=authenticatedUser && authenticatedUser.getIsRoot();

             case USER_VIEW:
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sun Feb 9 06:49:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sun Feb 9 10:00:28 2014 UTC
@@ -12,7 +12,7 @@
     REPOSITORY_IMPORT(TargetType.REPOSITORY),
     REPOSITORY_LIST(null),
     REPOSITORY_LIST_INACTIVE(null),
-    REPOSITORY_CREATE(null),
+    REPOSITORY_ADD(null),

     USER_VIEW(TargetType.USER),
     USER_EDIT(TargetType.USER),
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Sun Feb 9 10:00:28 2014 UTC
@@ -39,6 +39,12 @@
 home.viewCriteriaType.mostrecent=Most Recently Updated
 home.viewCriteriaType.mostviewed=Most Viewed

+addEditRepository.code.required=The code is required.
+addEditRepository.code.pattern=The code must consist of between 2 and 16 lower case letters and digits. +addEditRepository.code.unique=The code nominated for this repository is already in-use; nominate a different code.
+addEditRepository.url.required=The url to the .hpkr data must be supplied.
+addEditRepository.url.url=The url must be well-formed.
+
 # Test case

 test.it=Test line for integration testing
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Feb 8 11:31:22 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sun Feb 9 10:00:28 2014 UTC
@@ -76,6 +76,7 @@
<value>/js/app/controller/runtimeinformationcontroller.js</value> <value>/js/app/controller/listrepositoriescontroller.js</value> <value>/js/app/controller/viewrepositorycontroller.js</value> + <value>/js/app/controller/addeditrepositorycontroller.js</value>

<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sat Feb 8 11:31:22 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sun Feb 9 10:00:28 2014 UTC
@@ -50,6 +50,9 @@
<li ng-show="!amShowingInactive" show-if-permission="'REPOSITORY_LIST_INACTIVE'"> <a href="" ng-click="goShowInactive()">Show inactive repositories</a>
         </li>
+        <li show-if-permission="'REPOSITORY_ADD'">
+            <a href="" ng-click="goAdd()">Add repository</a>
+        </li>
     </ul>

 </div>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js Sun Feb 9 10:00:28 2014 UTC
@@ -106,6 +106,9 @@
                 return $scope.hasMore ? [] : ['disabled'];
             }

+            $scope.goAdd = function() {
+                $location.path('/addrepository').search({});
+            }

         }
     ]
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sat Feb 8 09:30:39 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sun Feb 9 10:00:28 2014 UTC
@@ -32,6 +32,9 @@
<li ng-show="repository.active" repository="repository" show-if-repository-permission="'REPOSITORY_IMPORT'">
             <a href="" ng-click="goTriggerImport()">Trigger import</a>
         </li>
+ <li repository="repository" show-if-repository-permission="'REPOSITORY_EDIT'">
+            <a href="" ng-click="goEdit()">Edit</a>
+        </li>
     </ul>

 </div>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sat Feb 8 09:30:39 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sun Feb 9 10:00:28 2014 UTC
@@ -62,6 +62,10 @@
             $scope.goDeactivate = function() {
                 updateActive(false);
             }
+
+            $scope.goEdit = function() {
+ $location.path('/editrepository/' + $scope.repository.code);
+            }

             /**
* <p>This function will initiate an import of a repository. These run sequentially so it may not happen
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sun Feb 9 10:00:28 2014 UTC
@@ -8,6 +8,28 @@
         '$routeProvider',
         function($routeProvider) {
             $routeProvider
+                .when('/addrepository',{
+                    controller:'AddEditRepositoryController',
+ templateUrl:'/js/app/controller/addeditrepository.html',
+                    resolve: {
+                        'architectures':[
+                            'referenceData', function(referenceData) {
+                                return referenceData.architectures();
+                            }
+                        ]
+                    }
+                })
+                .when('/editrepository/:code',{
+                    controller:'AddEditRepositoryController',
+ templateUrl:'/js/app/controller/addeditrepository.html',
+                    resolve: {
+                        'architectures':[
+                            'referenceData', function(referenceData) {
+                                return referenceData.architectures();
+                            }
+                        ]
+                    }
+                })
.when('/viewrepository/:code',{controller:'ViewRepositoryController', templateUrl:'/js/app/controller/viewrepository.html'}) .when('/listrepositories',{controller:'ListRepositoriesController', templateUrl:'/js/app/controller/listrepositories.html'}) .when('/runtimeinformation',{controller:'RuntimeInformationController', templateUrl:'/js/app/controller/runtimeinformation.html'})
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sun Feb 9 10:00:28 2014 UTC
@@ -13,6 +13,20 @@

             var BreadcrumbsService = {

+                createEditRepository : function(repository) {
+                    return {
+                        title : 'Edit ' + repository.code,
+                        path : '/editrepository/' + repository.code
+                    }
+                },
+
+                createAddRepository : function(repository) {
+                    return {
+                        title : 'Add Repository',
+                        path : '/addrepository'
+                    }
+                },
+
                 createViewRepository : function(repository) {
                   return {
                       title : repository.code,
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sun Feb 9 10:00:28 2014 UTC
@@ -43,6 +43,33 @@
                 handleUnknownLocation : function() {
                     $log.error('unknown location; ' + $location.path());
                     $location.path("/error").search({});
+                },
+
+                /**
+ * <p>Splay validation failures into the form. The validation failures are objects that are + * returned as part of the error envelope in RPC invocations. This method will return true + * if all of the validation errors were able to be assigned to models in the form.</p>
+                 */
+
+ handleValidationFailures : function(validationFailures, form) {
+                    var result = true;
+
+                    if(validationFailures) {
+                        _.each(validationFailures, function(vf) {
+                            if(vf.property && vf.property.length) {
+                                var model = form[vf.property];
+
+                                if(model) {
+                                    model.$setValidity(vf.message, false);
+                                }
+                                else {
+                                    result = false;
+                                }
+                            }
+                        })
+                    }
+
+                    return result;
                 }

             };

==============================================================================
Revision: 68df28d58697
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  9 10:38:52 2014 UTC
Log:      + documentation update
+ changes around add / edit repository

http://code.google.com/p/haiku-depot-web-app/source/detail?r=68df28d58697

Modified:
 /haikudepotserver-docs/src/main/latex/docs/part-api.tex
 /haikudepotserver-docs/src/main/latex/docs/part-deployment.tex
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java
 /haikudepotserver-webapp/src/main/resources/messages.properties
/haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js

=======================================
--- /haikudepotserver-docs/src/main/latex/docs/part-api.tex Sun Feb 2 09:38:27 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-api.tex Sun Feb 9 10:38:52 2014 UTC
@@ -129,6 +129,7 @@
 \end{itemize}

 \subsubsection{Import Repository Data}
+\label{api-importrepositorydata}

This API provides a mechanism by which an external client is able to trigger the application to start importing package-related data from a remote repository. This API is provided as REST because the client is likely to be scripted using a scripting language and REST is the most appropriate protocol to employ in this situation. This invocation will trigger the import process, but the import process will execute in a background thread in the application server and will not block the client.

=======================================
--- /haikudepotserver-docs/src/main/latex/docs/part-deployment.tex Wed Jan 29 10:34:56 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-deployment.tex Sun Feb 9 10:38:52 2014 UTC
@@ -44,27 +44,7 @@
 \subsection{Setting Up Repositories}
 \label{settinguprepositories}

-{\it This section is temporary until a user-interface exists for this.}
-
-The application server will pull ``.hpkr'' files from remote repositories that contain information about the packages at that repository. At the time of writing, it is necessary to configure the repositories by hand. A repository can be added using a SQL shell.
-
-In this ficticious example, an ``.hpkr'' file has been placed in the temporary directory. The following SQL command can be executed to add a repository that pulls the ``.hpkr'' from the temporary directory. In reality, the real repositories will have an internet-accessible URL.
-
-\begin{verbatim}
-INSERT INTO
-  haikudepot.repository (
-    id, active, create_timestamp, modify_timestamp,
-    architecture_id, code, url)
-  VALUES (
-    nextval('haikudepot.repository_seq'), true, now(), now(),
-    (SELECT id FROM haikudepot.architecture WHERE code='x86'),
-    'test',
-    'file:///tmp/repo.hpkr');
-\end{verbatim}
-
-In order to prompt the system to import this ".hpkr" file and populate some repository data into the system, you can use the curl tool as follows;
-
-\framebox{\tt curl "http://localhost:8080/importrepositorydata?code=test"}
+The application server will pull ``.hpkr'' files from remote repositories that contain information about the packages at that repository. Authenticated as root, it is possible to use the ``more'' link at the top of the home page to get to the repositories, to add a repository and to trigger the import of a repository. There also exists an HTTP GET invocation (see \ref{api-importrepositorydata}) that can be made to trigger the import of a repository from another system.

 \subsection{Accessing the Web Environment}

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java Sun Feb 9 10:38:52 2014 UTC
@@ -18,6 +18,8 @@
 import org.haikuos.haikudepotserver.dataobjects.support.Coded;
import org.haikuos.haikudepotserver.dataobjects.support.CreateAndModifyTimestamped;

+import java.net.MalformedURLException;
+import java.net.URL;
 import java.util.List;

public class Repository extends _Repository implements CreateAndModifyTimestamped, Coded {
@@ -43,6 +45,15 @@
validationResult.addFailure(new BeanValidationFailure(this,CODE_PROPERTY,"malformed"));
             }
         }
+
+        if(null != getUrl()) {
+            try {
+                new URL(getUrl());
+            }
+            catch(MalformedURLException mue) {
+ validationResult.addFailure(new BeanValidationFailure(this,URL_PROPERTY,"malformed"));
+            }
+        }

     }

=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Sun Feb 9 10:38:52 2014 UTC
@@ -43,7 +43,7 @@
addEditRepository.code.pattern=The code must consist of between 2 and 16 lower case letters and digits. addEditRepository.code.unique=The code nominated for this repository is already in-use; nominate a different code.
 addEditRepository.url.required=The url to the .hpkr data must be supplied.
-addEditRepository.url.url=The url must be well-formed.
+addEditRepository.url.malformed=The url must be well-formed.

 # Test case

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html Sun Feb 9 10:38:52 2014 UTC
@@ -36,9 +36,10 @@
<div class="form-control-group" ng-class="deriveFormControlsContainerClasses('url')">
             <input
                     id="url"
-                    type="url"
+                    type="text"
                     size="64"
                     name="url"
+                    ng-change="urlChanged()"
                     ng-required="true"
                     ng-model="workingRepository.url"></input>
<error-messages key-prefix="addEditRepository.url" error="addEditRepositoryForm.url.$error"></error-messages>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js Sun Feb 9 10:38:52 2014 UTC
@@ -33,6 +33,10 @@
             $scope.codeChanged = function() {
$scope.addEditRepositoryForm.code.$setValidity('unique',true);
             }
+
+            $scope.urlChanged = function() {
+ $scope.addEditRepositoryForm.url.$setValidity('malformed',true);
+            }

             refreshRepository();


Other related posts:

  • » [haiku-depot-web] [haiku-depot-web-app] 7 new revisions pushed by haiku.li...@xxxxxxxxx on 2014-02-09 10:42 GMT - haiku-depot-web-app