From 1bf692f0e6157af8aaf36dfa7bfac318642ed1f9 Mon Sep 17 00:00:00 2001 From: anditsung Date: Thu, 30 Apr 2020 15:34:22 +0700 Subject: [PATCH] * change usage novauser.gates.user.model to auth.providers.users.model * make custom ActionResource * change CustomAuthorize to NovauserAuthorize. * patch NovaServiceProvider * add route web to test permission on local * add action to navigation. --- config/novauser.php | 9 +- dist/js/tool.js | 2 +- .../permission-checkbox/form/List.vue | 12 +- resources/views/navigation.blade.php | 13 + routes/web.php | 32 ++ src/Commands/Init.php | 2 +- src/Commands/Install.php | 27 +- src/Http/Controllers/Auth/LoginController.php | 2 +- src/Http/Middleware/CustomAuthorize.php | 18 - src/Http/Middleware/NovauserAuthorize.php | 70 +++ src/Models/ActionEvent.php | 490 ++++++++++++++++++ src/Models/Permission.php | 3 +- src/Models/Role.php | 3 +- src/Nova/ActionResource.php | 161 ++++++ src/NovaUserManagement.php | 4 +- src/ToolServiceProvider.php | 4 +- 16 files changed, 812 insertions(+), 40 deletions(-) delete mode 100644 src/Http/Middleware/CustomAuthorize.php create mode 100644 src/Http/Middleware/NovauserAuthorize.php create mode 100644 src/Models/ActionEvent.php create mode 100644 src/Nova/ActionResource.php diff --git a/config/novauser.php b/config/novauser.php index 595f88b..457dce7 100644 --- a/config/novauser.php +++ b/config/novauser.php @@ -54,6 +54,11 @@ 'binds' => [ 'login' => \Tsung\NovaUserManagement\Http\Controllers\Auth\LoginController::class, - 'authorize' => \Tsung\NovaUserManagement\Http\Middleware\CustomAuthorize::class, - ] + 'authorize' => \Tsung\NovaUserManagement\Http\Middleware\NovauserAuthorize::class, + ], + + /* + * set true to show actions resource on navigation + */ + 'show-actions' => false, ]; diff --git a/dist/js/tool.js b/dist/js/tool.js index 5f8828c..d9c604b 100644 --- a/dist/js/tool.js +++ b/dist/js/tool.js @@ -1 +1 @@ -!function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=3)}([function(t,e){t.exports=function(t,e,n,r,i,o){var a,u=t=t||{},s=typeof t.default;"object"!==s&&"function"!==s||(a=t,u=t.default);var c,f="function"==typeof u?u.options:u;if(e&&(f.render=e.render,f.staticRenderFns=e.staticRenderFns,f._compiled=!0),n&&(f.functional=!0),i&&(f._scopeId=i),o?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),r&&r.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(o)},f._ssrRegister=c):r&&(c=r),c){var l=f.functional,p=l?f.render:f.beforeCreate;l?(f._injectStyles=c,f.render=function(t,e){return c.call(e),p(t,e)}):f.beforeCreate=p?[].concat(p,c):[c]}return{esModule:a,exports:u,options:f}}},function(t,e){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(t){"object"==typeof window&&(n=window)}t.exports=n},function(t,e,n){var r;r=function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.i=function(t){return t},n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=49)}([function(t,e,n){"use strict";var r=n(48),i=n(158),o=Object.prototype.toString;function a(t){return"[object Array]"===o.call(t)}function u(t){return null!==t&&"object"==typeof t}function s(t){return"[object Function]"===o.call(t)}function c(t,e){if(null!==t&&void 0!==t)if("object"!=typeof t&&(t=[t]),a(t))for(var n=0,r=t.length;n=200&&t<300}};s.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],function(t){s.headers[t]={}}),r.forEach(["post","put","patch"],function(t){s.headers[t]=r.merge(o)}),t.exports=s}).call(e,n(77))},function(t,e,n){"use strict";e.__esModule=!0;var r,i=n(115),o=(r=i)&&r.__esModule?r:{default:r};e.default=function(t,e,n){return e in t?(0,o.default)(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},function(t,e,n){var r=n(9),i=n(1).document,o=r(i)&&r(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e){t.exports=!0},function(t,e,n){"use strict";var r=n(14);t.exports.f=function(t){return new function(t){var e,n;this.promise=new t(function(t,r){if(void 0!==e||void 0!==n)throw TypeError("Bad Promise constructor");e=t,n=r}),this.resolve=r(e),this.reject=r(n)}(t)}},function(t,e,n){var r=n(11).f,i=n(17),o=n(2)("toStringTag");t.exports=function(t,e,n){t&&!i(t=n?t:t.prototype,o)&&r(t,o,{configurable:!0,value:e})}},function(t,e,n){var r=n(62)("keys"),i=n(67);t.exports=function(t){return r[t]||(r[t]=i(t))}},function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},function(t,e,n){var r=n(56),i=n(27);t.exports=function(t){return r(i(t))}},function(t,e,n){var r=n(12).Symbol;t.exports=r},function(t,e,n){var r=n(172),i=n(191);t.exports=function(t,e){var n=i(t,e);return r(n)?n:void 0}},function(t,e){t.exports=function(t,e){return t===e||t!=t&&e!=e}},function(t,e){t.exports=function(t){return t}},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.mapProps=void 0;var r,i=n(236),o=(r=i)&&r.__esModule?r:{default:r};var a={shownViaNewRelationModal:{type:Boolean,default:!1},resourceId:{type:[Number,String]},resourceName:{type:String},field:{type:Object,required:!0},viaResource:{type:String,required:!1},viaResourceId:{type:[String,Number],required:!1},viaRelationship:{type:String,required:!1}};e.mapProps=function(t){return o.default.pick(a,t)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=["1/2","1/3","2/3","1/4","3/4","1/5","2/5","3/5","4/5","1/6","5/6"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(156);Object.defineProperty(e,"default",{enumerable:!0,get:function(){return o(r).default}}),Object.defineProperty(e,"Form",{enumerable:!0,get:function(){return o(r).default}});var i=n(68);function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"Errors",{enumerable:!0,get:function(){return o(i).default}})},function(t,e,n){"use strict";(function(e){var r=n(0),i=n(103),o=n(106),a=n(112),u=n(110),s=n(47),c="undefined"!=typeof window&&window.btoa&&window.btoa.bind(window)||n(105);t.exports=function(t){return new Promise(function(f,l){var p=t.data,d=t.headers;r.isFormData(p)&&delete d["Content-Type"];var h=new XMLHttpRequest,v="onreadystatechange",g=!1;if("test"===e.env.NODE_ENV||"undefined"==typeof window||!window.XDomainRequest||"withCredentials"in h||u(t.url)||(h=new window.XDomainRequest,v="onload",g=!0,h.onprogress=function(){},h.ontimeout=function(){}),t.auth){var m=t.auth.username||"",y=t.auth.password||"";d.Authorization="Basic "+c(m+":"+y)}if(h.open(t.method.toUpperCase(),o(t.url,t.params,t.paramsSerializer),!0),h.timeout=t.timeout,h[v]=function(){if(h&&(4===h.readyState||g)&&(0!==h.status||h.responseURL&&0===h.responseURL.indexOf("file:"))){var e="getAllResponseHeaders"in h?a(h.getAllResponseHeaders()):null,n={data:t.responseType&&"text"!==t.responseType?h.response:h.responseText,status:1223===h.status?204:h.status,statusText:1223===h.status?"No Content":h.statusText,headers:e,config:t,request:h};i(f,l,n),h=null}},h.onerror=function(){l(s("Network Error",t,null,h)),h=null},h.ontimeout=function(){l(s("timeout of "+t.timeout+"ms exceeded",t,"ECONNABORTED",h)),h=null},r.isStandardBrowserEnv()){var _=n(108),b=(t.withCredentials||u(t.url))&&t.xsrfCookieName?_.read(t.xsrfCookieName):void 0;b&&(d[t.xsrfHeaderName]=b)}if("setRequestHeader"in h&&r.forEach(d,function(t,e){void 0===p&&"content-type"===e.toLowerCase()?delete d[e]:h.setRequestHeader(e,t)}),t.withCredentials&&(h.withCredentials=!0),t.responseType)try{h.responseType=t.responseType}catch(e){if("json"!==t.responseType)throw e}"function"==typeof t.onDownloadProgress&&h.addEventListener("progress",t.onDownloadProgress),"function"==typeof t.onUploadProgress&&h.upload&&h.upload.addEventListener("progress",t.onUploadProgress),t.cancelToken&&t.cancelToken.promise.then(function(t){h&&(h.abort(),l(t),h=null)}),void 0===p&&(p=null),h.send(p)})}}).call(e,n(77))},function(t,e,n){"use strict";function r(t){this.message=t}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,t.exports=r},function(t,e,n){"use strict";t.exports=function(t){return!(!t||!t.__CANCEL__)}},function(t,e,n){"use strict";var r=n(102);t.exports=function(t,e,n,i,o){var a=new Error(t);return r(a,e,n,i,o)}},function(t,e,n){"use strict";t.exports=function(t,e){return function(){for(var n=new Array(arguments.length),r=0;rn;)e.push(arguments[n++]);return m[++g]=function(){u("function"==typeof t?t:Function(t),e)},r(g),g},d=function(t){delete m[t]},"process"==n(15)(l)?r=function(t){l.nextTick(a(y,t,1))}:v&&v.now?r=function(t){v.now(a(y,t,1))}:h?(o=(i=new h).port2,i.port1.onmessage=_,r=a(o.postMessage,o,1)):f.addEventListener&&"function"==typeof postMessage&&!f.importScripts?(r=function(t){f.postMessage(t+"","*")},f.addEventListener("message",_,!1)):r="onreadystatechange"in c("script")?function(t){s.appendChild(c("script")).onreadystatechange=function(){s.removeChild(this),y.call(t)}}:function(t){setTimeout(a(y,t,1),0)}),t.exports={set:p,clear:d}},function(t,e,n){var r=n(34),i=Math.min;t.exports=function(t){return t>0?i(r(t),9007199254740991):0}},function(t,e,n){var r=n(27);t.exports=function(t){return Object(r(t))}},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.record(e)}return r(t,[{key:"all",value:function(){return this.errors}},{key:"has",value:function(t){var e=this.errors.hasOwnProperty(t);e||(e=Object.keys(this.errors).filter(function(e){return e.startsWith(t+".")||e.startsWith(t+"[")}).length>0);return e}},{key:"first",value:function(t){return this.get(t)[0]}},{key:"get",value:function(t){return this.errors[t]||[]}},{key:"any",value:function(){return Object.keys(this.errors).length>0}},{key:"record",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.errors=t}},{key:"clear",value:function(t){if(t){var e=Object.assign({},this.errors);Object.keys(e).filter(function(e){return e===t||e.startsWith(t+".")||e.startsWith(t+"[")}).forEach(function(t){return delete e[t]}),this.errors=e}else this.errors={}}}]),t}();e.default=i},function(t,e,n){var r=n(179),i=n(231),o=n(13),a=n(232),u=n(72),s=n(233),c=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=o(t),f=!n&&i(t),l=!n&&!f&&a(t),p=!n&&!f&&!l&&s(t),d=n||f||l||p,h=d?r(t.length,String):[],v=h.length;for(var g in t)!e&&!c.call(t,g)||d&&("length"==g||l&&("offset"==g||"parent"==g)||p&&("buffer"==g||"byteLength"==g||"byteOffset"==g)||u(g,v))||h.push(g);return h}},function(t,e,n){(function(e){var n="object"==typeof e&&e&&e.Object===Object&&e;t.exports=n}).call(e,n(78))},function(t,e){var n=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");t.exports=function(t){return n.test(t)}},function(t,e){var n=9007199254740991,r=/^(?:0|[1-9]\d*)$/;t.exports=function(t,e){var i=typeof t;return!!(e=null==e?n:e)&&("number"==i||"symbol"!=i&&r.test(t))&&t>-1&&t%1==0&&t-1&&t%1==0&&t<=n}},function(t,e,n){var r=n(180);t.exports=function(t){return null==t?"":r(t)}},function(t,e){var n,r,i=t.exports={};function o(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function u(t){if(n===setTimeout)return setTimeout(t,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(t){n=o}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(t){r=a}}();var s,c=[],f=!1,l=-1;function p(){f&&s&&(f=!1,s.length?c=s.concat(c):l=-1,c.length&&d())}function d(){if(!f){var t=u(p);f=!0;for(var e=c.length;e;){for(s=c,c=[];++l1)for(var n=1;n1&&void 0!==arguments[1]?arguments[1]:null;return this.viaManyToMany?this.detachResources(t):Nova.request({url:"/nova-api/"+this.resourceName,method:"delete",params:(0,o.default)({},this.queryString,{resources:a(t)})}).then(n||function(){e.deleteModalOpen=!1,e.getResources()})},deleteSelectedResources:function(){this.deleteResources(this.selectedResources)},deleteAllMatchingResources:function(){var t=this;return this.viaManyToMany?this.detachAllMatchingResources():Nova.request({url:this.deleteAllMatchingResourcesEndpoint,method:"delete",params:(0,o.default)({},this.queryString,{resources:"all"})}).then(function(){t.deleteModalOpen=!1,t.getResources()})},detachResources:function(t){var e=this;return Nova.request({url:"/nova-api/"+this.resourceName+"/detach",method:"delete",params:(0,o.default)({},this.queryString,{resources:a(t)})}).then(function(){e.deleteModalOpen=!1,e.getResources()})},detachAllMatchingResources:function(){var t=this;return Nova.request({url:"/nova-api/"+this.resourceName+"/detach",method:"delete",params:(0,o.default)({},this.queryString,{resources:"all"})}).then(function(){t.deleteModalOpen=!1,t.getResources()})},forceDeleteResources:function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return Nova.request({url:"/nova-api/"+this.resourceName+"/force",method:"delete",params:(0,o.default)({},this.queryString,{resources:a(t)})}).then(n||function(){e.deleteModalOpen=!1,e.getResources()})},forceDeleteSelectedResources:function(){this.forceDeleteResources(this.selectedResources)},forceDeleteAllMatchingResources:function(){var t=this;return Nova.request({url:this.forceDeleteSelectedResourcesEndpoint,method:"delete",params:(0,o.default)({},this.queryString,{resources:"all"})}).then(function(){t.deleteModalOpen=!1,t.getResources()})},restoreResources:function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return Nova.request({url:"/nova-api/"+this.resourceName+"/restore",method:"put",params:(0,o.default)({},this.queryString,{resources:a(t)})}).then(n||function(){e.restoreModalOpen=!1,e.getResources()})},restoreSelectedResources:function(){this.restoreResources(this.selectedResources)},restoreAllMatchingResources:function(){var t=this;return Nova.request({url:this.restoreAllMatchingResourcesEndpoint,method:"put",params:(0,o.default)({},this.queryString,{resources:"all"})}).then(function(){t.restoreModalOpen=!1,t.getResources()})}},computed:{deleteAllMatchingResourcesEndpoint:function(){return this.lens?"/nova-api/"+this.resourceName+"/lens/"+this.lens:"/nova-api/"+this.resourceName},forceDeleteSelectedResourcesEndpoint:function(){return this.lens?"/nova-api/"+this.resourceName+"/lens/"+this.lens+"/force":"/nova-api/"+this.resourceName+"/force"},restoreAllMatchingResourcesEndpoint:function(){return this.lens?"/nova-api/"+this.resourceName+"/lens/"+this.lens+"/restore":"/nova-api/"+this.resourceName+"/restore"},queryString:function(){return{search:this.currentSearch,filters:this.encodedFilters,trashed:this.currentTrashed,viaResource:this.viaResource,viaResourceId:this.viaResourceId,viaRelationship:this.viaRelationship}}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=a(n(52)),i=a(n(26)),o=a(n(51));a(n(228)),a(n(230));function a(t){return t&&t.__esModule?t:{default:t}}e.default={methods:{clearSelectedFilters:function(){var t=(0,o.default)(r.default.mark(function t(e){var n;return r.default.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:if(!e){t.next=5;break}return t.next=3,this.$store.dispatch(this.resourceName+"/resetFilterState",{resourceName:this.resourceName,lens:e});case 3:t.next=7;break;case 5:return t.next=7,this.$store.dispatch(this.resourceName+"/resetFilterState",{resourceName:this.resourceName});case 7:this.updateQueryString((n={},(0,i.default)(n,this.pageParameter,1),(0,i.default)(n,this.filterParameter,""),n));case 8:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),filterChanged:function(){var t;this.updateQueryString((t={},(0,i.default)(t,this.pageParameter,1),(0,i.default)(t,this.filterParameter,this.$store.getters[this.resourceName+"/currentEncodedFilters"]),t))},initializeFilters:function(){var t=(0,o.default)(r.default.mark(function t(e){return r.default.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return this.$store.commit(this.resourceName+"/clearFilters"),t.next=3,this.$store.dispatch(this.resourceName+"/fetchFilters",{resourceName:this.resourceName,viaResource:this.viaResource,viaResourceId:this.viaResourceId,viaRelationship:this.viaRelationship,lens:e});case 3:return t.next=5,this.initializeState(e);case 5:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),initializeState:function(){var t=(0,o.default)(r.default.mark(function t(e){return r.default.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:if(!this.initialEncodedFilters){t.next=5;break}return t.next=3,this.$store.dispatch(this.resourceName+"/initializeCurrentFilterValuesFromQueryString",this.initialEncodedFilters);case 3:t.next=7;break;case 5:return t.next=7,this.$store.dispatch(this.resourceName+"/resetFilterState",{resourceName:this.resourceName,lens:e});case 7:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}()},computed:{filterParameter:function(){return this.resourceName+"_filter"}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(41);e.default={props:(0,r.mapProps)(["shownViaNewRelationModal","field","viaResource","viaResourceId","viaRelationship","resourceName"]),data:function(){return{value:""}},mounted:function(){var t=this;this.setInitialValue(),this.field.fill=this.fill,Nova.$on(this.field.attribute+"-value",function(e){t.value=e})},destroyed:function(){Nova.$off(this.field.attribute+"-value")},methods:{setInitialValue:function(){this.value=void 0!==this.field.value&&null!==this.field.value?this.field.value:""},fill:function(t){t.append(this.field.attribute,String(this.value))},handleChange:function(t){this.value=t}},computed:{isReadonly:function(){return this.field.readonly||_.get(this.field,"extraAttributes.readonly")}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(43);e.default={props:{errors:{default:function(){return new r.Errors}}},data:function(){return{errorClass:"border-danger"}},computed:{errorClasses:function(){return this.hasError?[this.errorClass]:[]},fieldAttribute:function(){return this.field.attribute},validationKey:function(){return this.field.validationKey},hasError:function(){return this.errors.has(this.validationKey)},firstError:function(){if(this.hasError)return this.errors.first(this.validationKey)}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=a(n(52)),i=a(n(51)),o=a(n(42));function a(t){return t&&t.__esModule?t:{default:t}}e.default={props:{loadCards:{type:Boolean,default:!0}},data:function(){return{cards:[]}},created:function(){this.fetchCards()},watch:{cardsEndpoint:function(){this.fetchCards()}},methods:{fetchCards:function(){var t=(0,i.default)(r.default.mark(function t(){var e,n;return r.default.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:if(!this.loadCards){t.next=6;break}return t.next=3,Nova.request().get(this.cardsEndpoint,{params:this.extraCardParams});case 3:e=t.sent,n=e.data,this.cards=n;case 6:case"end":return t.stop()}},t,this)}));return function(){return t.apply(this,arguments)}}()},computed:{shouldShowCards:function(){return this.cards.length>0},smallCards:function(){return _.filter(this.cards,function(t){return-1!==o.default.indexOf(t.width)})},largeCards:function(){return _.filter(this.cards,function(t){return"full"==t.width})},extraCardParams:function(){return null}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={methods:{toAppTimezone:function(t){return t?moment.tz(t,this.userTimezone).clone().tz(Nova.config.timezone).format("YYYY-MM-DD HH:mm:ss"):t},fromAppTimezone:function(t){return t?moment.tz(t,Nova.config.timezone).clone().tz(this.userTimezone).format("YYYY-MM-DD HH:mm:ss"):t},localizeDateTimeField:function(t){if(!t.value)return t.value;var e=moment.tz(t.value,Nova.config.timezone).clone().tz(this.userTimezone);return t.format?e.format(t.format):this.usesTwelveHourTime?e.format("YYYY-MM-DD h:mm:ss A"):e.format("YYYY-MM-DD HH:mm:ss")},localizeDateField:function(t){if(!t.value)return t.value;var e=moment.tz(t.value,Nova.config.timezone).clone().tz(this.userTimezone);return t.format?e.format(t.format):e.format("YYYY-MM-DD")}},computed:{userTimezone:function(){return Nova.config.userTimezone?Nova.config.userTimezone:moment.tz.guess()},usesTwelveHourTime:function(){return _.endsWith((new Date).toLocaleString(),"AM")||_.endsWith((new Date).toLocaleString(),"PM")}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,i=n(227),o=(r=i)&&r.__esModule?r:{default:r};e.default={methods:{updateQueryString:function(t){this.$router.push({query:(0,o.default)(t,this.$route.query)})}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={computed:{resourceInformation:function(){var t=this;return _.find(Nova.config.resources,function(e){return e.uriKey==t.resourceName})},viaResourceInformation:function(){var t=this;if(this.viaResource)return _.find(Nova.config.resources,function(e){return e.uriKey==t.viaResource})},authorizedToCreate:function(){return this.resourceInformation.authorizedToCreate}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,i=n(26),o=(r=i)&&r.__esModule?r:{default:r};e.default={methods:{selectPreviousPage:function(){this.updateQueryString((0,o.default)({},this.pageParameter,this.currentPage-1))},selectNextPage:function(){this.updateQueryString((0,o.default)({},this.pageParameter,this.currentPage+1))}},computed:{currentPage:function(){return parseInt(this.$route.query[this.pageParameter]||1)}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,i=n(26),o=(r=i)&&r.__esModule?r:{default:r};e.default={data:function(){return{perPage:25}},methods:{initializePerPageFromQueryString:function(){this.perPage=this.currentPerPage},perPageChanged:function(){this.updateQueryString((0,o.default)({},this.perPageParameter,this.perPage))}},computed:{currentPerPage:function(){return this.$route.query[this.perPageParameter]||25}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,i=n(226),o=(r=i)&&r.__esModule?r:{default:r};e.default={data:function(){return{search:"",selectedResource:"",availableResources:[]}},methods:{selectResource:function(t){this.selectedResource=t},handleSearchCleared:function(){this.availableResources=[]},clearSelection:function(){this.selectedResource="",this.availableResources=[]},performSearch:function(t){var e=this;this.search=t;var n=t.trim();""!=n&&this.debouncer(function(){e.getAvailableResources(n)},500)},debouncer:(0,o.default)(function(t){return t()},500)}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={data:function(){return{withTrashed:!1}},methods:{toggleWithTrashed:function(){this.withTrashed=!this.withTrashed},enableWithTrashed:function(){this.withTrashed=!0},disableWithTrashed:function(){this.withTrashed=!1}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t){return(0,o.default)(t)};var r,i=n(241),o=(r=i)&&r.__esModule?r:{default:r}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,i=n(50),o=(r=i)&&r.__esModule?r:{default:r};e.default=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100;return o.default.all([t,new o.default(function(t){setTimeout(function(){return t()},e)})]).then(function(t){return t[0]})}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t,e){return t>1||0==t?r.Inflector.pluralize(e):r.Inflector.singularize(e)};var r=n(49)},function(t,e,n){"use strict";var r={uncountableWords:["equipment","information","rice","money","species","series","fish","sheep","moose","deer","news"],pluralRules:[[new RegExp("(m)an$","gi"),"$1en"],[new RegExp("(pe)rson$","gi"),"$1ople"],[new RegExp("(child)$","gi"),"$1ren"],[new RegExp("^(ox)$","gi"),"$1en"],[new RegExp("(ax|test)is$","gi"),"$1es"],[new RegExp("(octop|vir)us$","gi"),"$1i"],[new RegExp("(alias|status)$","gi"),"$1es"],[new RegExp("(bu)s$","gi"),"$1ses"],[new RegExp("(buffal|tomat|potat)o$","gi"),"$1oes"],[new RegExp("([ti])um$","gi"),"$1a"],[new RegExp("sis$","gi"),"ses"],[new RegExp("(?:([^f])fe|([lr])f)$","gi"),"$1$2ves"],[new RegExp("(hive)$","gi"),"$1s"],[new RegExp("([^aeiouy]|qu)y$","gi"),"$1ies"],[new RegExp("(x|ch|ss|sh)$","gi"),"$1es"],[new RegExp("(matr|vert|ind)ix|ex$","gi"),"$1ices"],[new RegExp("([m|l])ouse$","gi"),"$1ice"],[new RegExp("(quiz)$","gi"),"$1zes"],[new RegExp("s$","gi"),"s"],[new RegExp("$","gi"),"s"]],singularRules:[[new RegExp("(m)en$","gi"),"$1an"],[new RegExp("(pe)ople$","gi"),"$1rson"],[new RegExp("(child)ren$","gi"),"$1"],[new RegExp("([ti])a$","gi"),"$1um"],[new RegExp("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$","gi"),"$1$2sis"],[new RegExp("(hive)s$","gi"),"$1"],[new RegExp("(tive)s$","gi"),"$1"],[new RegExp("(curve)s$","gi"),"$1"],[new RegExp("([lr])ves$","gi"),"$1f"],[new RegExp("([^fo])ves$","gi"),"$1fe"],[new RegExp("([^aeiouy]|qu)ies$","gi"),"$1y"],[new RegExp("(s)eries$","gi"),"$1eries"],[new RegExp("(m)ovies$","gi"),"$1ovie"],[new RegExp("(x|ch|ss|sh)es$","gi"),"$1"],[new RegExp("([m|l])ice$","gi"),"$1ouse"],[new RegExp("(bus)es$","gi"),"$1"],[new RegExp("(o)es$","gi"),"$1"],[new RegExp("(shoe)s$","gi"),"$1"],[new RegExp("(cris|ax|test)es$","gi"),"$1is"],[new RegExp("(octop|vir)i$","gi"),"$1us"],[new RegExp("(alias|status)es$","gi"),"$1"],[new RegExp("^(ox)en","gi"),"$1"],[new RegExp("(vert|ind)ices$","gi"),"$1ex"],[new RegExp("(matr)ices$","gi"),"$1ix"],[new RegExp("(quiz)zes$","gi"),"$1"],[new RegExp("s$","gi"),""]],nonTitlecasedWords:["and","or","nor","a","an","the","so","but","to","of","at","by","from","into","on","onto","off","out","in","over","with","for"],idSuffix:new RegExp("(_ids|_id)$","g"),underbar:new RegExp("_","g"),spaceOrUnderbar:new RegExp("[ _]","g"),uppercase:new RegExp("([A-Z])","g"),underbarPrefix:new RegExp("^_"),applyRules:function(t,e,n,r){if(r)t=r;else if(!(n.indexOf(t.toLowerCase())>-1))for(var i=0;i>8-u%1*8)){if((n=o.charCodeAt(u+=.75))>255)throw new i;e=e<<8|n}return a}},function(t,e,n){"use strict";var r=n(0);function i(t){return encodeURIComponent(t).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}t.exports=function(t,e,n){if(!e)return t;var o;if(n)o=n(e);else if(r.isURLSearchParams(e))o=e.toString();else{var a=[];r.forEach(e,function(t,e){null!==t&&void 0!==t&&(r.isArray(t)?e+="[]":t=[t],r.forEach(t,function(t){r.isDate(t)?t=t.toISOString():r.isObject(t)&&(t=JSON.stringify(t)),a.push(i(e)+"="+i(t))}))}),o=a.join("&")}return o&&(t+=(-1===t.indexOf("?")?"?":"&")+o),t}},function(t,e,n){"use strict";t.exports=function(t,e){return e?t.replace(/\/+$/,"")+"/"+e.replace(/^\/+/,""):t}},function(t,e,n){"use strict";var r=n(0);t.exports=r.isStandardBrowserEnv()?{write:function(t,e,n,i,o,a){var u=[];u.push(t+"="+encodeURIComponent(e)),r.isNumber(n)&&u.push("expires="+new Date(n).toGMTString()),r.isString(i)&&u.push("path="+i),r.isString(o)&&u.push("domain="+o),!0===a&&u.push("secure"),document.cookie=u.join("; ")},read:function(t){var e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove:function(t){this.write(t,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},function(t,e,n){"use strict";t.exports=function(t){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(t)}},function(t,e,n){"use strict";var r=n(0);t.exports=r.isStandardBrowserEnv()?function(){var t,e=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function i(t){var r=t;return e&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return t=i(window.location.href),function(e){var n=r.isString(e)?i(e):e;return n.protocol===t.protocol&&n.host===t.host}}():function(){return!0}},function(t,e,n){"use strict";var r=n(0);t.exports=function(t,e){r.forEach(t,function(n,r){r!==e&&r.toUpperCase()===e.toUpperCase()&&(t[e]=n,delete t[r])})}},function(t,e,n){"use strict";var r=n(0),i=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];t.exports=function(t){var e,n,o,a={};return t?(r.forEach(t.split("\n"),function(t){if(o=t.indexOf(":"),e=r.trim(t.substr(0,o)).toLowerCase(),n=r.trim(t.substr(o+1)),e){if(a[e]&&i.indexOf(e)>=0)return;a[e]="set-cookie"===e?(a[e]?a[e]:[]).concat([n]):a[e]?a[e]+", "+n:n}}),a):a}},function(t,e,n){"use strict";t.exports=function(t){return function(e){return t.apply(null,e)}}},function(t,e,n){t.exports={default:n(117),__esModule:!0}},function(t,e,n){t.exports={default:n(118),__esModule:!0}},function(t,e,n){"use strict";e.__esModule=!0;var r,i=n(114),o=(r=i)&&r.__esModule?r:{default:r};e.default=o.default||function(t){for(var e=1;ef;)if((u=s[f++])!=u)return!0}else for(;c>f;f++)if((t||f in s)&&s[f]===n)return t||f||0;return!t&&-1}}},function(t,e,n){var r=n(16),i=n(127),o=n(126),a=n(4),u=n(65),s=n(146),c={},f={};(e=t.exports=function(t,e,n,l,p){var d,h,v,g,m=p?function(){return t}:s(t),y=r(n,l,e?2:1),_=0;if("function"!=typeof m)throw TypeError(t+" is not iterable!");if(o(m)){for(d=u(t.length);d>_;_++)if((g=e?y(a(h=t[_])[0],h[1]):y(t[_]))===c||g===f)return g}else for(v=m.call(t);!(h=v.next()).done;)if((g=i(v,y,h.value,e))===c||g===f)return g}).BREAK=c,e.RETURN=f},function(t,e,n){t.exports=!n(5)&&!n(29)(function(){return 7!=Object.defineProperty(n(28)("div"),"a",{get:function(){return 7}}).a})},function(t,e){t.exports=function(t,e,n){var r=void 0===n;switch(e.length){case 0:return r?t():t.call(n);case 1:return r?t(e[0]):t.call(n,e[0]);case 2:return r?t(e[0],e[1]):t.call(n,e[0],e[1]);case 3:return r?t(e[0],e[1],e[2]):t.call(n,e[0],e[1],e[2]);case 4:return r?t(e[0],e[1],e[2],e[3]):t.call(n,e[0],e[1],e[2],e[3])}return t.apply(n,e)}},function(t,e,n){var r=n(10),i=n(2)("iterator"),o=Array.prototype;t.exports=function(t){return void 0!==t&&(r.Array===t||o[i]===t)}},function(t,e,n){var r=n(4);t.exports=function(t,e,n,i){try{return i?e(r(n)[0],n[1]):e(n)}catch(e){var o=t.return;throw void 0!==o&&r(o.call(t)),e}}},function(t,e,n){"use strict";var r=n(133),i=n(61),o=n(32),a={};n(7)(a,n(2)("iterator"),function(){return this}),t.exports=function(t,e,n){t.prototype=r(a,{next:i(1,n)}),o(t,e+" Iterator")}},function(t,e,n){var r=n(2)("iterator"),i=!1;try{var o=[7][r]();o.return=function(){i=!0},Array.from(o,function(){throw 2})}catch(t){}t.exports=function(t,e){if(!e&&!i)return!1;var n=!1;try{var o=[7],a=o[r]();a.next=function(){return{done:n=!0}},o[r]=function(){return a},t(o)}catch(t){}return n}},function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},function(t,e,n){var r=n(1),i=n(64).set,o=r.MutationObserver||r.WebKitMutationObserver,a=r.process,u=r.Promise,s="process"==n(15)(a);t.exports=function(){var t,e,n,c=function(){var r,i;for(s&&(r=a.domain)&&r.exit();t;){i=t.fn,t=t.next;try{i()}catch(r){throw t?n():e=void 0,r}}e=void 0,r&&r.enter()};if(s)n=function(){a.nextTick(c)};else if(!o||r.navigator&&r.navigator.standalone)if(u&&u.resolve){var f=u.resolve(void 0);n=function(){f.then(c)}}else n=function(){i.call(r,c)};else{var l=!0,p=document.createTextNode("");new o(c).observe(p,{characterData:!0}),n=function(){p.data=l=!l}}return function(r){var i={fn:r,next:void 0};e&&(e.next=i),t||(t=i,n()),e=i}}},function(t,e,n){"use strict";var r=n(58),i=n(135),o=n(138),a=n(66),u=n(56),s=Object.assign;t.exports=!s||n(29)(function(){var t={},e={},n=Symbol(),r="abcdefghijklmnopqrst";return t[n]=7,r.split("").forEach(function(t){e[t]=t}),7!=s({},t)[n]||Object.keys(s({},e)).join("")!=r})?function(t,e){for(var n=a(t),s=arguments.length,c=1,f=i.f,l=o.f;s>c;)for(var p,d=u(arguments[c++]),h=f?r(d).concat(f(d)):r(d),v=h.length,g=0;v>g;)l.call(d,p=h[g++])&&(n[p]=d[p]);return n}:s},function(t,e,n){var r=n(4),i=n(134),o=n(54),a=n(33)("IE_PROTO"),u=function(){},s=function(){var t,e=n(28)("iframe"),r=o.length;for(e.style.display="none",n(55).appendChild(e),e.src="javascript:",(t=e.contentWindow.document).open(),t.write(" diff --git a/resources/views/navigation.blade.php b/resources/views/navigation.blade.php index 09b77ad..f9b0dc2 100644 --- a/resources/views/navigation.blade.php +++ b/resources/views/navigation.blade.php @@ -48,6 +48,19 @@ @endcan + @if(Config::get('novauser.show-actions') == true) +
  • + + {{ __("Actions") }} + +
  • + @endif + @endcanany diff --git a/routes/web.php b/routes/web.php index ec7f1f2..e23a5b4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -9,3 +9,35 @@ // Route::get('/password/reset/{token}', \Laravel\Nova\Http\Controllers\ResetPasswordController::class . '@showResetForm')->name('password.reset'); // Route::post('/password/reset', \Laravel\Nova\Http\Controllers\ResetPasswordController::class . '@reset'); //}); + +Route::group(['middleware'=> 'web'], function() { + + // to check user permissions + if(app()->environment('local')) { + Route::get('/perm', function() { + $user = Auth()->user(); + if($user) { + $permissionModel = config('novauser.gates.permission.model'); + $permissions = $permissionModel::all(); + echo "PERMISSION FOR {$user->name}
    "; + foreach($permissions as $permission) { + $text = ""; + if($user->can($permission->name)) { + $text .= "allow"; + } + else { + $text .= "not allow"; + } + $text .= " to {$permission->name}
    "; + echo $text; + } + } + else { + echo "NO USER DETECTED"; + } + die(); + }); + } +}); + + diff --git a/src/Commands/Init.php b/src/Commands/Init.php index 8ee58b5..072393c 100644 --- a/src/Commands/Init.php +++ b/src/Commands/Init.php @@ -16,7 +16,7 @@ class Init extends Command public function handle() { $guard = config('nova.guard') ?: config('auth.defaults.guard'); - $userModel = config('novauser.gates.user.model'); + $userModel = config('auth.providers.users.model'); $roleModel = config('novauser.gates.role.model'); $permissionModel = config('novauser.gates.permission.model'); diff --git a/src/Commands/Install.php b/src/Commands/Install.php index d55881b..60d52d5 100644 --- a/src/Commands/Install.php +++ b/src/Commands/Install.php @@ -14,31 +14,46 @@ class Install extends Command public function handle() { - $this->info('Replacing Default User Model'); $this->replaceUserModel(); - $this->info("Done"); - $this->info('Replacing Default User Nova'); $this->replaceUserNova(); - $this->info("Done"); - $this->info("Publish Novauser Config"); $this->publishConfig(); - $this->info('Done'); + + $this->patchingNovaServiceProviderGate(); } private function replaceUserModel() { + $this->info('Replacing Default User Model'); copy(__DIR__.'/../Stub/Models/User.stub', app_path('User.php')); + $this->info("Done"); } private function replaceUserNova() { + $this->info('Replacing Default User Nova'); copy(__DIR__.'/../Stub/Nova/User.stub', app_path('Nova/User.php')); + $this->info("Done"); } private function publishConfig() { + $this->info("Publish Novauser Config"); $this->call('vendor:publish', ['--tag' => 'novauser-config']); + $this->info('Done'); + } + + private function patchingNovaServiceProviderGate() + { + $novaServiceProviderPath = app_path('Providers/NovaServiceProvider.php'); + + $this->info("Patching NovaServiceProvider gate method"); + $gate_regex = "/in_array[\(\$\w\-\>\,\[\s\/]+.+/"; + $patchGate = '$user->hasPermissionTo(\'viewNova\');'; + $novaServiceProviderContent = file_get_contents($novaServiceProviderPath); + $novaServiceProviderContent = preg_replace($gate_regex, $patchGate, $novaServiceProviderContent); + file_put_contents($novaServiceProviderPath, $novaServiceProviderContent); + $this->info("Done"); } } diff --git a/src/Http/Controllers/Auth/LoginController.php b/src/Http/Controllers/Auth/LoginController.php index eba9d87..9109eeb 100644 --- a/src/Http/Controllers/Auth/LoginController.php +++ b/src/Http/Controllers/Auth/LoginController.php @@ -20,7 +20,7 @@ protected function authenticated(Request $request, $user) private function isActive($user) { - $userModel = config('novauser.gates.user.model'); + $userModel = config('auth.providers.users.model'); $user = $userModel::where($this->username(), $user)->first(); if($user) { return $user->is_active; diff --git a/src/Http/Middleware/CustomAuthorize.php b/src/Http/Middleware/CustomAuthorize.php deleted file mode 100644 index f964866..0000000 --- a/src/Http/Middleware/CustomAuthorize.php +++ /dev/null @@ -1,18 +0,0 @@ -logonUserStillActive($request); + + $this->forgetCachedPermissions($request); + + return $next($request); + } + else { + // if the user dont have viewNova permissions then redirect to '/' + // return abort(403); + return redirect('/'); + } + } + + /** + * @param $request + * + * this method will check user is still active, if not logout the user, + * note: nova will redirect the user if dont have viewNova permission but the user still login + * dont need this cause web user and admin user using the same model + */ + private function logonUserStillActive($request) + { + $user = $request->user(); + if ($user) { + if( ! $user = auth()->user()->is_active) { + auth()->logout(); + } + } + } + + /** + * @param $request + * + * this method will reset cache for permission after adding + * laravel nova using cache permission, so it need to be reset before useable + */ + private function forgetCachedPermissions($request) + { + if ( $request->is('nova-api/*/detach') || $request->is('nova-api/*/*/attach*/*') ) { +// $permissionKey = Nova::resourceForModel(app(PermissionRegistrar::class)->getPermissionClass())::uriKey(); +// +// if ($request->viaRelationship === $permissionKey) { +// app(PermissionRegistrar::class)->forgetCachedPermissions(); +// } + + /* + * if the request->viaRelationship is roles / permissions will reset permission cache + */ + if( $request->viaRelationship === "roles" || $request->viaRelationship === "permissions" ) { + app(PermissionRegistrar::class)->forgetCachedPermissions(); + } + } + } + +} diff --git a/src/Models/ActionEvent.php b/src/Models/ActionEvent.php new file mode 100644 index 0000000..11bb0a8 --- /dev/null +++ b/src/Models/ActionEvent.php @@ -0,0 +1,490 @@ + 'array', + 'changes' => 'array', + ]; + + /** + * The storage format of the model's date columns. + * + * @var string + */ + protected $dateFormat = 'Y-m-d H:i:s'; + + /** + * Get the user that initiated the action. + */ + public function user() + { + return $this->belongsTo(config('auth.providers.users.model'), 'user_id'); + } + + /** + * Get the target of the action for user interface linking. + */ + public function target() + { + return $this->morphTo('target', 'target_type', 'target_id')->withTrashed(); + } + + /** + * Create a new action event instance for a resource creation. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model + */ + public static function forResourceCreate($user, $model) + { + $relations = self::forResourceCreateUpdateRelations($model); + + $changes = array_merge($model->attributesToArray(), $relations['dirty']); + + return new static([ + 'batch_id' => (string) Str::orderedUuid(), + 'user_id' => $user->getAuthIdentifier(), + 'name' => 'Create', + 'actionable_type' => $model->getMorphClass(), + 'actionable_id' => $model->getKey(), + 'target_type' => $model->getMorphClass(), + 'target_id' => $model->getKey(), + 'model_type' => $model->getMorphClass(), + 'model_id' => $model->getKey(), + 'fields' => '', + 'original' => null, + //'changes' => $model->attributesToArray(), + 'changes' => $changes, + 'status' => 'finished', + 'exception' => '', + ]); + } + + /** + * Create a new action event instance for a resource update. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model + */ + public static function forResourceUpdate($user, $model) + { + $relations = self::forResourceCreateUpdateRelations($model); + + $original = array_intersect_key($model->getOriginal(), $model->getDirty()); + $original = array_merge($original, $relations['original']); + $dirty = array_merge($model->getDirty(), $relations['dirty']); + + return new static([ + 'batch_id' => (string) Str::orderedUuid(), + 'user_id' => $user->getAuthIdentifier(), + 'name' => 'Update', + 'actionable_type' => $model->getMorphClass(), + 'actionable_id' => $model->getKey(), + 'target_type' => $model->getMorphClass(), + 'target_id' => $model->getKey(), + 'model_type' => $model->getMorphClass(), + 'model_id' => $model->getKey(), + 'fields' => '', + //'original' => array_intersect_key($model->getOriginal(), $model->getDirty()), + 'original' => $original, + //'changes' => $model->getDirty(), + 'changes' => $dirty, + 'status' => 'finished', + 'exception' => '', + ]); + } + + private static function forResourceCreateUpdateRelations($parentModel) + { + $relations = array( + 'original' => array(), + 'dirty' => array() + ); + + foreach($parentModel->relations as $relationName => $models) { + foreach($models as $model) { + if(!array_key_exists($relationName, $relations['dirty'])) { + $relations['dirty'][$relationName] = $model->name; + } + else { + $relations['dirty'][$relationName] .= ", {$model->name}"; + } + } + } + return $relations; + } + + /** + * Create a new action event instance for an attached resource. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Illuminate\Database\Eloquent\Model $parent + * @param \Illuminate\Database\Eloquent\Model $pivot + * @return \Illuminate\Database\Eloquent\Model + */ + public static function forAttachedResource(NovaRequest $request, $parent, $pivot) + { + $target = Nova::modelInstanceForKey($request->relatedResource)->find($pivot[$pivot->getRelatedKey()]); + + return new static([ + 'batch_id' => (string) Str::orderedUuid(), + 'user_id' => $request->user()->getAuthIdentifier(), + 'name' => 'Attach', + 'actionable_type' => $parent->getMorphClass(), + 'actionable_id' => $parent->getKey(), + //'target_type' => Nova::modelInstanceForKey($request->relatedResource)->getMorphClass(), + 'target_type' => $target->getMorphClass(), + 'target_id' => $target->getKey(), + 'model_type' => $pivot->getMorphClass(), + 'model_id' => $pivot->getKey(), + 'fields' => '', + 'original' => null, + 'changes' => $pivot->attributesToArray(), + 'status' => 'finished', + 'exception' => '', + ]); + } + + /** + * Create a new action event instance for an attached resource update. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Illuminate\Database\Eloquent\Model $parent + * @param \Illuminate\Database\Eloquent\Model $pivot + * @return \Illuminate\Database\Eloquent\Model + */ + public static function forAttachedResourceUpdate(NovaRequest $request, $parent, $pivot) + { + return new static([ + 'batch_id' => (string) Str::orderedUuid(), + 'user_id' => $request->user()->getAuthIdentifier(), + 'name' => 'Update Attached', + 'actionable_type' => $parent->getMorphClass(), + 'actionable_id' => $parent->getKey(), + 'target_type' => Nova::modelInstanceForKey($request->relatedResource)->getMorphClass(), + 'target_id' => $request->relatedResourceId, + 'model_type' => $pivot->getMorphClass(), + 'model_id' => $pivot->getKey(), + 'fields' => '', + 'original' => array_intersect_key($pivot->getOriginal(), $pivot->getDirty()), + 'changes' => $pivot->getDirty(), + 'status' => 'finished', + 'exception' => '', + ]); + } + + /** + * Create new action event instances for resource deletes. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param \Illuminate\Support\Collection $models + * @return \Illuminate\Support\Collection + */ + public static function forResourceDelete($user, Collection $models) + { + return static::forSoftDeleteAction('Delete', $user, $models); + } + + /** + * Create new action event instances for resource restorations. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param \Illuminate\Support\Collection $models + * @return \Illuminate\Support\Collection + */ + public static function forResourceRestore($user, Collection $models) + { + return static::forSoftDeleteAction('Restore', $user, $models); + } + + /** + * Create new action event instances for resource soft deletions. + * + * @param string $action + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param \Illuminate\Support\Collection $models + * @return \Illuminate\Support\Collection + */ + public static function forSoftDeleteAction($action, $user, Collection $models) + { + $batchId = (string) Str::orderedUuid(); + + return $models->map(function ($model) use ($action, $user, $batchId) { + return new static([ + 'batch_id' => $batchId, + 'user_id' => $user->getAuthIdentifier(), + 'name' => $action, + 'actionable_type' => $model->getMorphClass(), + 'actionable_id' => $model->getKey(), + 'target_type' => $model->getMorphClass(), + 'target_id' => $model->getKey(), + 'model_type' => $model->getMorphClass(), + 'model_id' => $model->getKey(), + 'fields' => '', + 'original' => null, + 'changes' => null, + 'status' => 'finished', + 'exception' => '', + 'created_at' => new DateTime, + 'updated_at' => new DateTime, + ]); + }); + } + + /** + * Create new action event instances for resource detachments. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param \Illuminate\Database\Eloquent\Model $parent + * @param \Illuminate\Support\Collection $models + * @param string $pivotClass + * @return \Illuminate\Support\Collection + */ + public static function forResourceDetach($user, $parent, Collection $models, $pivotClass) + { + $batchId = (string) Str::orderedUuid(); + + return $models->map(function ($model) use ($user, $parent, $pivotClass, $batchId) { + + $target = Nova::modelInstanceForKey(request()->viaRelationship); + + return new static([ + 'batch_id' => $batchId, + 'user_id' => $user->getAuthIdentifier(), + 'name' => 'Detach', + 'actionable_type' => $parent->getMorphClass(), + 'actionable_id' => $parent->getKey(), + //'target_type' => $model->getMorphClass(), + 'target_type' => $target->getMorphClass(), + 'target_id' => $model->getKey(), + 'model_type' => $pivotClass, + 'model_id' => null, + 'fields' => '', + 'original' => null, + 'changes' => null, + 'status' => 'finished', + 'exception' => '', + 'created_at' => new DateTime, + 'updated_at' => new DateTime, + ]); + }); + } + + /** + * Create the action records for the given models. + * + * @param \Laravel\Nova\Http\Requests\ActionRequest $request + * @param \Laravel\Nova\Actions\Action $action + * @param string $batchId + * @param \Illuminate\Support\Collection $models + * @param string $status + * @return void + */ + public static function createForModels(ActionRequest $request, Action $action, + $batchId, Collection $models, $status = 'running') + { + $models = $models->map(function ($model) use ($request, $action, $batchId, $status) { + return array_merge( + static::defaultAttributes($request, $action, $batchId, $status), + [ + 'actionable_id' => $request->actionableKey($model), + 'target_id' => $request->targetKey($model), + 'model_id' => $model->getKey(), + ] + ); + }); + + $models->chunk(50)->each(function ($models) { + static::insert($models->all()); + }); + + static::prune($models); + } + + /** + * Get the default attributes for creating a new action event. + * + * @param \Laravel\Nova\Http\Requests\ActionRequest $request + * @param \Laravel\Nova\Actions\Action $action + * @param string $batchId + * @param string $status + * @return array + */ + public static function defaultAttributes(ActionRequest $request, Action $action, + $batchId, $status = 'running') + { + if ($request->isPivotAction()) { + $pivotClass = $request->pivotRelation()->getPivotClass(); + + $modelType = collect(Relation::$morphMap)->filter(function ($model, $alias) use ($pivotClass) { + return $model === $pivotClass; + })->keys()->first() ?? $pivotClass; + } else { + $modelType = $request->actionableModel()->getMorphClass(); + } + + return [ + 'batch_id' => $batchId, + 'user_id' => $request->user()->getAuthIdentifier(), + 'name' => $action->name(), + 'actionable_type' => $request->actionableModel()->getMorphClass(), + 'target_type' => $request->model()->getMorphClass(), + 'model_type' => $modelType, + 'fields' => serialize($request->resolveFieldsForStorage()), + 'original' => null, + 'changes' => null, + 'status' => $status, + 'exception' => '', + 'created_at' => new DateTime, + 'updated_at' => new DateTime, + ]; + } + + /** + * Prune the action events for the given types. + * + * @param \Illuminate\Support\Collection $models + * @param int $limit + */ + public static function prune($models, $limit = 25) + { + $models->each(function ($model) use ($limit) { + static::where('actionable_id', $model['actionable_id']) + ->where('actionable_type', $model['actionable_type']) + ->whereNotIn('id', function ($query) use ($model, $limit) { + $query->select('id')->fromSub( + static::select('id')->orderBy('id', 'desc') + ->where('actionable_id', $model['actionable_id']) + ->where('actionable_type', $model['actionable_type']) + ->limit($limit)->toBase(), + 'action_events_temp' + ); + })->delete(); + }); + } + + /** + * Mark the given batch as running. + * + * @param string $batchId + * @return int + */ + public static function markBatchAsRunning($batchId) + { + return static::where('batch_id', $batchId) + ->whereNotIn('status', ['finished', 'failed'])->update([ + 'status' => 'running', + ]); + } + + /** + * Mark the given batch as finished. + * + * @param string $batchId + * @return int + */ + public static function markBatchAsFinished($batchId) + { + return static::where('batch_id', $batchId) + ->whereNotIn('status', ['finished', 'failed'])->update([ + 'status' => 'finished', + ]); + } + + /** + * Mark a given action event record as finished. + * + * @param string $batchId + * @param \Illuminate\Database\Eloquent\Model $model + * @return int + */ + public static function markAsFinished($batchId, $model) + { + return static::updateStatus($batchId, $model, 'finished'); + } + + /** + * Mark the given batch as failed. + * + * @param string $batchId + * @param \Throwable $e + * @return int + */ + public static function markBatchAsFailed($batchId, $e = null) + { + return static::where('batch_id', $batchId) + ->whereNotIn('status', ['finished', 'failed'])->update([ + 'status' => 'failed', + 'exception' => $e ? (string) $e : '', + ]); + } + + /** + * Mark a given action event record as failed. + * + * @param string $batchId + * @param \Illuminate\Database\Eloquent\Model $model + * @param \Throwable|string $e + * @return int + */ + public static function markAsFailed($batchId, $model, $e = null) + { + return static::updateStatus($batchId, $model, 'failed', $e); + } + + /** + * Update the status of a given action event. + * + * @param string $batchId + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $status + * @param \Throwable|string $e + * @return int + */ + public static function updateStatus($batchId, $model, $status, $e = null) + { + return static::where('batch_id', $batchId) + ->where('model_type', $model->getMorphClass()) + ->where('model_id', $model->getKey()) + ->update(['status' => $status, 'exception' => (string) $e]); + } + + /** + * Get the table associated with the model. + * + * @return string + */ + public function getTable() + { + return 'action_events'; + } +} diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 59d0d03..b7a5a82 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -4,7 +4,6 @@ namespace Tsung\NovaUserManagement\Models; -use App\User; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Laravel\Nova\Actions\Actionable; use Spatie\Permission\Models\Permission as SpatiePermissionModel; @@ -33,6 +32,6 @@ protected static function boot() public function user() : BelongsTo { - return $this->belongsTo(User::class); + return $this->belongsTo(config('auth.providers.users.model')); } } diff --git a/src/Models/Role.php b/src/Models/Role.php index cc5baa6..adbbade 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -3,7 +3,6 @@ namespace Tsung\NovaUserManagement\Models; -use App\User; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Laravel\Nova\Actions\Actionable; use Spatie\Permission\Models\Role as SpatieRoleModel; @@ -31,6 +30,6 @@ protected static function boot() public function user() : BelongsTo { - return $this->belongsTo(User::class); + return $this->belongsTo(config('auth.providers.users.model')); } } diff --git a/src/Nova/ActionResource.php b/src/Nova/ActionResource.php new file mode 100644 index 0000000..6e7c40d --- /dev/null +++ b/src/Nova/ActionResource.php @@ -0,0 +1,161 @@ +user->name ?? $this->user->email ?? __('Nova User'); + }), + + MorphToActionTarget::make(__('Action Target'), 'target'), + + Status::make(__('Action Status'), 'status', function ($value) { + return __(ucfirst($value)); + })->loadingWhen([__('Waiting'), __('Running')])->failedWhen([__('Failed')]), + + $this->when(isset($this->original), function () { + return KeyValue::make(__('Original'), 'original'); + }), + + $this->when(isset($this->changes), function () { + return KeyValue::make(__('Changes'), 'changes'); + }), + + Textarea::make(__('Exception'), 'exception'), + + DateTime::make(__('Action Happened At'), 'created_at')->exceptOnForms(), + ]; + } + + /** + * Build an "index" query for the given resource. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function indexQuery(NovaRequest $request, $query) + { + return $query->with('user'); + } + + /** + * Determine if this resource is available for navigation. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + public static function availableForNavigation(Request $request) + { + return false; + } + + /** + * Determine if this resource is searchable. + * + * @return bool + */ + public static function searchable() + { + return false; + } + + /** + * Get the displayable label of the resource. + * + * @return string + */ + public static function label() + { + return __('Actions'); + } + + /** + * Get the displayable singular label of the resource. + * + * @return string + */ + public static function singularLabel() + { + return __('Action'); + } + + /** + * Get the URI key for the resource. + * + * @return string + */ + public static function uriKey() + { + return 'action-events'; + } +} diff --git a/src/NovaUserManagement.php b/src/NovaUserManagement.php index fcd325a..ec716f8 100644 --- a/src/NovaUserManagement.php +++ b/src/NovaUserManagement.php @@ -2,11 +2,9 @@ namespace Tsung\NovaUserManagement; -use App\Nova\User; + use Laravel\Nova\Nova; use Laravel\Nova\Tool; -use Tsung\NovaUserManagement\Nova\Permission; -use Tsung\NovaUserManagement\Nova\Role; class NovaUserManagement extends Tool { diff --git a/src/ToolServiceProvider.php b/src/ToolServiceProvider.php index 64aa8f4..a629da8 100644 --- a/src/ToolServiceProvider.php +++ b/src/ToolServiceProvider.php @@ -66,7 +66,7 @@ public function registerPolicies() }); Gate::policy(config('novauser.gates.action.model'), config('novauser.gates.action.policy')); - Gate::policy(config('novauser.gates.user.model'), config('novauser.gates.user.policy')); + Gate::policy(config('auth.providers.users.model'), config('novauser.gates.user.policy')); Gate::policy(config('novauser.gates.role.model'), config('novauser.gates.role.policy')); Gate::policy(config('novauser.gates.permission.model'), config('novauser.gates.permission.policy')); } @@ -93,7 +93,7 @@ protected function routes() ->prefix('nova-vendor/nova-user-management') ->group(__DIR__.'/../routes/api.php'); - $this->loadRoutesFrom(__DIR__ . '/../routes/web.php'); + $this->loadRoutesFrom(__DIR__.'/../routes/web.php'); } /**