From ab1857e558f5d6e1c099fd726fe58168d81f1717 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 11 Jun 2014 14:46:44 +0200 Subject: [PATCH] First attempt of organization for resource module. --- app/coffee/{taiga.coffee => app.coffee} | 6 +- app/coffee/base.coffee | 23 ++ .../{taigaBacklog.coffee => backlog.coffee} | 0 .../{taigaCommon.coffee => common.coffee} | 0 app/coffee/modules/resources/init.coffee | 61 +++++ app/coffee/modules/resources/model.coffee | 239 ++++++++++++++++++ .../modules/resources/repository.coffee | 131 ++++++++++ app/coffee/modules/resources/storage.coffee | 52 ++++ app/coffee/modules/resources/urls.coffee | 45 ++++ app/coffee/services/resources.coffee | 0 gulpfile.js | 5 +- 11 files changed, 555 insertions(+), 7 deletions(-) rename app/coffee/{taiga.coffee => app.coffee} (97%) create mode 100644 app/coffee/base.coffee rename app/coffee/modules/{taigaBacklog.coffee => backlog.coffee} (100%) rename app/coffee/modules/{taigaCommon.coffee => common.coffee} (100%) create mode 100644 app/coffee/modules/resources/init.coffee create mode 100644 app/coffee/modules/resources/model.coffee create mode 100644 app/coffee/modules/resources/repository.coffee create mode 100644 app/coffee/modules/resources/storage.coffee create mode 100644 app/coffee/modules/resources/urls.coffee delete mode 100644 app/coffee/services/resources.coffee diff --git a/app/coffee/taiga.coffee b/app/coffee/app.coffee similarity index 97% rename from app/coffee/taiga.coffee rename to app/coffee/app.coffee index 39997898..530dfecc 100644 --- a/app/coffee/taiga.coffee +++ b/app/coffee/app.coffee @@ -53,12 +53,10 @@ modules = [ "ngRoute", "ngAnimate", - "taigaConfig" - # Taiga specific modules - # "taigaCommon" + "taigaConfig", + "taigaResources", ] - angular.module("taigaLocalConfig", []).value("localconfig", {}) module = angular.module("taiga", modules) module.config(configure) diff --git a/app/coffee/base.coffee b/app/coffee/base.coffee new file mode 100644 index 00000000..04e2d303 --- /dev/null +++ b/app/coffee/base.coffee @@ -0,0 +1,23 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +class TaigaBase + +class TaigaService extends TaigaBase + +@.taiga.TaigaBase = TaigaBase +@.taiga.TaigaService = TaigaService diff --git a/app/coffee/modules/taigaBacklog.coffee b/app/coffee/modules/backlog.coffee similarity index 100% rename from app/coffee/modules/taigaBacklog.coffee rename to app/coffee/modules/backlog.coffee diff --git a/app/coffee/modules/taigaCommon.coffee b/app/coffee/modules/common.coffee similarity index 100% rename from app/coffee/modules/taigaCommon.coffee rename to app/coffee/modules/common.coffee diff --git a/app/coffee/modules/resources/init.coffee b/app/coffee/modules/resources/init.coffee new file mode 100644 index 00000000..4db1d1a2 --- /dev/null +++ b/app/coffee/modules/resources/init.coffee @@ -0,0 +1,61 @@ +init = (urls) -> + urls.update({ + "auth": "/api/v1/auth" + "auth-register": "/api/v1/auth/register" + "permissions": "/api/v1/permissions" + "roles": "/api/v1/roles" + "projects": "/api/v1/projects" + "memberships": "/api/v1/memberships" + "milestones": "/api/v1/milestones" + "userstories": "/api/v1/userstories" + "bulk-create-us": "/api/v1/userstories/bulk_create" + "bulk-update-us-order": "/api/v1/userstories/bulk_update_order" + "userstories-restore": "/api/v1/userstories/%s/restore" + "tasks": "/api/v1/tasks" + "bulk-create-tasks": "/api/v1/tasks/bulk_create" + "tasks-restore": "/api/v1/tasks/%s/restore" + "issues": "/api/v1/issues" + "issues-restore": "/api/v1/issues/%s/restore" + "wiki": "/api/v1/wiki" + "wiki-restore": "/api/v1/wiki/%s/restore" + "choices/userstory-statuses": "/api/v1/userstory-statuses" + "choices/userstory-statuses/bulk-update-order": "/api/v1/userstory-statuses/bulk_update_order" + "choices/points": "/api/v1/points" + "choices/points/bulk-update-order": "/api/v1/points/bulk_update_order" + "choices/task-statuses": "/api/v1/task-statuses" + "choices/task-statuses/bulk-update-order": "/api/v1/task-statuses/bulk_update_order" + "choices/issue-statuses": "/api/v1/issue-statuses" + "choices/issue-statuses/bulk-update-order": "/api/v1/issue-statuses/bulk_update_order" + "choices/issue-types": "/api/v1/issue-types" + "choices/issue-types/bulk-update-order": "/api/v1/issue-types/bulk_update_order" + "choices/priorities": "/api/v1/priorities" + "choices/priorities/bulk-update-order": "/api/v1/priorities/bulk_update_order" + "choices/severities": "/api/v1/severities" + "choices/severities/bulk-update-order": "/api/v1/severities/bulk_update_order" + "search": "/api/v1/search" + "sites": "/api/v1/sites" + "project-templates": "/api/v1/project-templates" + "site-members": "/api/v1/site-members" + "site-projects": "/api/v1/site-projects" + "users": "/api/v1/users" + "users-password-recovery": "/api/v1/users/password_recovery" + "users-change-password-from-recovery": "/api/v1/users/change_password_from_recovery" + "users-change-password": "/api/v1/users/change_password" + "resolver": "/api/v1/resolver" + "wiki-attachment": "/media/attachment-files/%s/wikipage/%s" + + # History + "history/userstory": "/api/v1/history/userstory" + "history/issue": "/api/v1/history/issue" + "history/task": "/api/v1/history/task" + "history/wiki": "/api/v1/history/wiki" + + # Attachments + "userstories/attachments": "/api/v1/userstories/attachments" + "issues/attachments": "/api/v1/issues/attachments" + "tasks/attachments": "/api/v1/tasks/attachments" + "wiki/attachments": "/api/v1/wiki/attachments" + }) + +module = angular.module("taigaResources", []) +module.run(["$tgUrls", init]) diff --git a/app/coffee/modules/resources/model.coffee b/app/coffee/modules/resources/model.coffee new file mode 100644 index 00000000..32210861 --- /dev/null +++ b/app/coffee/modules/resources/model.coffee @@ -0,0 +1,239 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +class Model + constructor: (name, data, dataTypes) -> + @._attrs = data + @._name = name + @._dataTypes = dataTypes + + @.setAttrs(data) + @.initialize() + + applyCasts: -> + for attrName, castName of @._dataTypes + castMethod = service.casts[castName] + if not castMethod + continue + + @._attrs[attrName] = castMethod(@._attrs[attrName]) + + getIdAttrName: -> + return "id" + + getUrl: -> + return "#{$gmUrls.api(@_name)}/#{@.getAttrs()[@.getIdAttrName()]}" + + getAttrs: (patch=false) -> + if patch + return _.extend({}, @._modifiedAttrs) + return _.extend({}, @._attrs, @._modifiedAttrs) + + setAttrs: (attrs) -> + @._attrs = attrs + @._modifiedAttrs = {} + + @.applyCasts() + @._isModified = false + + setAttr: (name, value) -> + @._modifiedAttrs[name] = value + @._isModified = true + + initialize: () -> + self = @ + + getter = (name) -> + return -> + if typeof(name) == 'string' and name.substr(0,2) == "__" + return self[name] + + if name not in _.keys(self._modifiedAttrs) + return self._attrs[name] + + return self._modifiedAttrs[name] + + setter = (name) -> + return (value) -> + if typeof(name) == 'string' and name.substr(0,2) == "__" + self[name] = value + return + + if self._attrs[name] != value + self._modifiedAttrs[name] = value + self._isModified = true + else + delete self._modifiedAttrs[name] + + return + + _.each @_attrs, (value, name) -> + options = + get: getter(name) + set: setter(name) + enumerable: true + configurable: true + + Object.defineProperty(self, name, options) + + serialize: () -> + data = + "data": _.clone(@_attrs) + "name": @_name + + return JSON.stringify(data) + + isModified: () -> + return this._isModified + + markSaved: () -> + @._isModified = false + @._attrs = @.getAttrs() + @._modifiedAttrs = {} + + revert: () -> + @_modifiedAttrs = {} + @_isModified = false + + @desSerialize = (sdata) -> + ddata = JSON.parse(sdata) + model = new Model(ddata.url, ddata.data) + return model + + +provider = ($q, $http, $gmUrls, $gmStorage) -> + headers = -> + token = $gmStorage.get('token') + if token + return {"Authorization":"Bearer #{token}"} + return {} + + service = {} + service.headers = headers + service.make_model = (name, data, cls=Model, dataTypes={}) -> + return new cls(name, data, dataTypes) + + service.create = (name, data, cls=Model, dataTypes={}, extraParams={}) -> + defered = $q.defer() + + params = { + method: "POST" + url: $gmUrls.api(name) + headers: headers() + data: JSON.stringify(data) + params: extraParams + } + + promise = $http(params) + promise.success (_data, _status) -> + defered.resolve(service.make_model(name, _data, cls, dataTypes)) + + promise.error (data, status) -> + defered.reject(data) + + return defered.promise + + service.remove = (model) -> + defered = $q.defer() + self = @ + + params = + method: "DELETE" + url: @getUrl() + headers: headers() + + promise = $http(params) + promise.success (data, status) -> + defered.resolve(self) + + promise.error (data, status) -> + defered.reject(self) + + return defered.promise + + service.save = (model, extraParams, patch=true) -> + self = @ + defered = $q.defer() + + if not @isModified() and patch + defered.resolve(self) + return defered.promise + + params = + url: @getUrl() + headers: headers(), + + if patch + params.method = "PATCH" + else + params.method = "PUT" + + params.data = JSON.stringify(@.getAttrs(patch)) + + params = _.extend({}, params, extraParams) + + promise = $http(params) + promise.success (data, status) -> + self._isModified = false + self._attrs = _.extend(self.getAttrs(), data) + self._modifiedAttrs = {} + + self.applyCasts() + defered.resolve(self) + + promise.error (data, status) -> + defered.reject(data) + + return defered.promise + + service.refresh = (model) -> + defered = $q.defer() + self = @ + + params = { + method: "GET", + url: @getUrl() + headers: headers() + } + + promise = $http(params) + promise.success (data, status) -> + self._modifiedAttrs = {} + self._attrs = data + self._isModified = false + self.applyCasts() + + defered.resolve(self) + + promise.error (data, status) -> + defered.reject([data, status]) + + return defered.promise + + service.cls = Model + service.casts = { + int: (value) -> + return parseInt(value, 10) + + float: (value) -> + return parseFloat(value, 10) + } + + return service + +module = angular.module("taigaResources") +module.factory("$tgModel", ["$q", "$http", "$tgUrls", "$tgStorage", provider]) diff --git a/app/coffee/modules/resources/repository.coffee b/app/coffee/modules/resources/repository.coffee new file mode 100644 index 00000000..6de5e143 --- /dev/null +++ b/app/coffee/modules/resources/repository.coffee @@ -0,0 +1,131 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +taiga = @.taiga + +class RepositoryService extends taiga.TaigaService + @.$inject = ["$http", "$q", "$tgModel", "$tgStorage"] + + constructor: (@http, @q, @model, @storage) -> + super() + + headers: -> + token = @.storage.get('token') + if token + return {"Authorization":"Bearer #{token}"} + return {} + + resolveUrlForModel: (model) -> + idAttrName = model.getIdAttrName() + return "#{@urls.resolve(model.name)}/#{model[idAttrName]}" + + create: (name, data, dataTypes={}, extraParams={}) -> + defered = @q.defer() + + params = { + method: "POST" + url: @urls.resolve(name) + headers: headers() + data: JSON.stringify(data) + params: extraParams + } + + promise = @http(params) + promise.success (_data, _status) => + defered.resolve(@model.make_model(name, _data, null, dataTypes)) + + promise.error (data, status) => + defered.reject(data) + + return defered.promise + + remove: (model) -> + defered = $q.defer() + + params = { + method: "DELETE" + url: @.resolveUrlForModel(model) + headers: @.headers() + } + + promise = @http(params) + promise.success (data, status) -> + defered.resolve(model) + + promise.error (data, status) -> + defered.reject(model) + + return defered.promise + + save: (model, extraParams, patch=true) -> + defered = $q.defer() + + if not model.isModified() and patch + defered.resolve(model) + return defered.promise + + params = { + url: @.resolveUrlForModel(model) + headers: @.headers() + } + + if patch + params.method = "PATCH" + else + params.method = "PUT" + + params.data = JSON.stringify(model.getAttrs(patch)) + params = _.extend({}, params, extraParams) + + promise = @http(params) + promise.success (data, status) => + model._isModified = false + model._attrs = _.extend(model.getAttrs(), data) + model._modifiedAttrs = {} + + model.applyCasts() + defered.resolve(model) + + promise.error (data, status) -> + defered.reject(data) + + return defered.promise + + refresh: (model) -> + defered = $q.defer() + params = { + method: "GET", + url: @.resolveUrlForModel(model) + headers: @.headers() + } + + promise = @http(params) + promise.success (data, status) -> + model._modifiedAttrs = {} + model._attrs = data + model._isModified = false + model.applyCasts() + defered.resolve(model) + + promise.error (data, status) -> + defered.reject(data) + + return defered.promise + + +module = angular.module("taigaResources") +module.service("resources", RepositoryService) diff --git a/app/coffee/modules/resources/storage.coffee b/app/coffee/modules/resources/storage.coffee new file mode 100644 index 00000000..cfdfeada --- /dev/null +++ b/app/coffee/modules/resources/storage.coffee @@ -0,0 +1,52 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +taiga = @.taiga + +class StorageService extends taiga.TaigaService + @.$inject = ["$rootScope"] + + constructor: ($rootScope) -> + super() + + get: (key, _default) -> + serializedValue = localStorage.getItem(key) + if serializedValue == null + return _default or null + + return JSON.parse(serializedValue) + + set: (key, val) -> + if _.isObject(key) + _.each key, (val, key) => + @set(key, val) + else + localStorage.setItem(key, JSON.stringify(val)) + + contains: (key) -> + value = @.get(key) + return (value != null) + + remove: (key) -> + localStorage.removeItem(key) + + clear: -> + localStorage.clear() + + +module = angular.module("taigaResources") +module.service("$tgStorage", StorageService) diff --git a/app/coffee/modules/resources/urls.coffee b/app/coffee/modules/resources/urls.coffee new file mode 100644 index 00000000..cdafe076 --- /dev/null +++ b/app/coffee/modules/resources/urls.coffee @@ -0,0 +1,45 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +format = (fmt, obj) -> + obj = _.clone(obj) + return fmt.replace /%s/g, (match) -> String(obj.shift()) + +class UrlsService + @.$inject = ["$tgConfig"] + + constructor: (@config) -> + @.urls = {} + @.host = config.get("host") + @.scheme = config.get("scheme") + + update: (urls) -> + @.urls = _.merge(@.urls, urls) + + resolve: -> + args = _.toArray(arguments) + + if args.length == 0 + throw Error("wrong arguments to setUrls") + + name = args.slice(0, 1)[0] + url = format(@.urls[name], args.slice(1)) + return format("%s://%s%s", [@.scheme, @.host, url]) + + +module = angular.module("taigaResources") +module.service('$tgUrls', UrlsService) diff --git a/app/coffee/services/resources.coffee b/app/coffee/services/resources.coffee deleted file mode 100644 index e69de29b..00000000 diff --git a/gulpfile.js b/gulpfile.js index b8d783f7..881ffcc6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -32,9 +32,8 @@ var paths = { sassMain: "app/styles/main.scss", css: "dist/styles/**/*.css", images: "app/images/**/*", - coffee: ["app/coffee/*.coffee", - "config/main.coffee", - "app/coffee/**/*.coffee"] + coffee: ["app/coffee/**/*.coffee", + "config/main.coffee"] }; // Ordered list of vendor/external libraries.