spaces work now!

This commit is contained in:
2020-08-31 14:33:31 +02:00
parent d915d1cd9b
commit e2ed2e2041
13 changed files with 176 additions and 200 deletions
+25 -108
View File
@@ -1,6 +1,4 @@
using FluentValidation.Results;
using Microsoft.Extensions.Options;
using Raven.Client.Documents;
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using Raven.Client.Documents.Session;
using System;
@@ -9,90 +7,60 @@ using System.Linq;
using System.Threading.Tasks;
using zero.Core.Entities;
using zero.Core.Extensions;
using zero.Core.Options;
using zero.Core.Plugins;
namespace zero.Core.Api
{
public class SpacesApi : ISpacesApi
public class SpacesApi : AppAwareBackofficeApi, ISpacesApi
{
protected IDocumentStore Raven { get; private set; }
protected IPermissionsApi PermissionsApi { get; private set; }
protected IZeroOptions Options { get; set; }
public SpacesApi(IDocumentStore raven, IPermissionsApi permissionsApi, IZeroOptions options)
public SpacesApi(IBackofficeStore store, IPermissionsApi permissionsApi) : base(store)
{
Raven = raven;
Scope.IncludeShared = true;
PermissionsApi = permissionsApi;
Options = options;
}
/// <inheritdoc />
public Space GetByAlias(string alias)
{
return Options.Spaces.GetAllItems().FirstOrDefault(x => x.Alias.Equals(alias, StringComparison.InvariantCultureIgnoreCase));
return GetAll().FirstOrDefault(x => x.Alias.Equals(alias, StringComparison.InvariantCultureIgnoreCase));
}
/// <inheritdoc />
public IReadOnlyCollection<Space> GetAll()
{
return Options.Spaces.GetAllItems();
return Backoffice.Options.Spaces.GetAllItems();
}
/// <inheritdoc />
public async Task<T> GetItem<T>(string alias) where T : SpaceContent
public async Task<ISpaceContent> GetItem(string alias, string id = null)
{
Space space = GetByAlias(alias);
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return await session.Query<SpaceContent>()
.Where(x => x.SpaceAlias == alias)
.ProjectInto<T>()
return await session.Query<ISpaceContent>()
.Scope(Scope)
.Where(x => x.SpaceAlias == space.Alias)
.WhereIf(x => x.Id == id, !id.IsNullOrEmpty())
.FirstOrDefaultAsync();
}
}
/// <inheritdoc />
public async Task<T> GetItem<T>(string alias, string id) where T : SpaceContent
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return await session.Query<SpaceContent>()
.Where(x => x.SpaceAlias == alias && x.Id == id)
.ProjectInto<T>()
.FirstOrDefaultAsync();
}
}
/// <inheritdoc />
public async Task<IList<T>> GetList<T>(string alias) where T : SpaceContent
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return await session.Query<SpaceContent>()
.Where(x => x.SpaceAlias == alias)
.ProjectInto<T>()
.ToListAsync();
}
}
/// <inheritdoc />
public async Task<ListResult<T>> GetListByQuery<T>(string alias, ListQuery<T> query, string appId = null) where T : SpaceContent
public async Task<ListResult<ISpaceContent>> GetListByQuery(string alias, ListQuery<ISpaceContent> query)
{
query.SearchSelector = item => item.Name;
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return await session.Query<T>()
.Scope(appId)
return await session.Query<ISpaceContent>()
.Scope(Scope)
.Where(x => x.SpaceAlias == alias)
.ToQueriedListAsync(query);
}
@@ -100,58 +68,17 @@ namespace zero.Core.Api
/// <inheritdoc />
public async Task<EntityResult<T>> Save<T>(string alias, T model) where T : SpaceContent
public async Task<EntityResult<ISpaceContent>> Save(string alias, ISpaceContent model)
{
Space space = GetByAlias(alias);
//RendererConfig config = GetEditorConfig(alias);
//if (config.Validator != null)
//{
// ValidationResult validation = await config.Validator.ValidateAsync(model);
// if (!validation.IsValid)
// {
// return EntityResult<T>.Fail(validation);
// }
//}
if (model.Id.IsNullOrEmpty())
{
model.AppId = "zero.applications.1-A"; // TODO real app id
model.CreatedDate = DateTimeOffset.Now;
}
model.SpaceAlias = alias;
model.Alias = Safenames.Alias(model.Name);
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
await session.StoreAsync(model);
await session.SaveChangesAsync();
}
return EntityResult<T>.Success(model);
model.SpaceAlias = GetByAlias(alias)?.Alias;
return await SaveModel(model, null);
}
/// <inheritdoc />
public async Task<EntityResult<object>> Delete(string alias, string id)
public async Task<EntityResult<ISpaceContent>> Delete(string alias, string id)
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
SpaceContent spaceContent = await session.LoadAsync<SpaceContent>(id);
if (spaceContent == null || !spaceContent.SpaceAlias.Equals(alias, StringComparison.InvariantCultureIgnoreCase))
{
return EntityResult.Fail("@errors.ondelete.idnotfound");
}
session.Delete(spaceContent);
await session.SaveChangesAsync();
}
return EntityResult.Success();
return await DeleteById<ISpaceContent>(id);
}
}
@@ -171,31 +98,21 @@ namespace zero.Core.Api
/// <summary>
/// Get editor item for a space
/// </summary>
Task<T> GetItem<T>(string alias) where T : SpaceContent;
/// <summary>
/// Get editor item for a space
/// </summary>
Task<T> GetItem<T>(string alias, string id) where T : SpaceContent;
/// <summary>
/// Get all list items by space alias
/// </summary>
Task<IList<T>> GetList<T>(string alias) where T : SpaceContent;
Task<ISpaceContent> GetItem(string alias, string id = null);
/// <summary>
/// Get all list items for a space (with query)
/// </summary>
Task<ListResult<T>> GetListByQuery<T>(string alias, ListQuery<T> query, string appId = null) where T : SpaceContent;
Task<ListResult<ISpaceContent>> GetListByQuery(string alias, ListQuery<ISpaceContent> query);
/// <summary>
/// Saves a content item in a space
/// </summary>
Task<EntityResult<T>> Save<T>(string alias, T model) where T : SpaceContent;
Task<EntityResult<ISpaceContent>> Save(string alias, ISpaceContent model);
/// <summary>
/// Deletes a space content item
/// </summary>
Task<EntityResult<object>> Delete(string alias, string id);
Task<EntityResult<ISpaceContent>> Delete(string alias, string id);
}
}
+19 -5
View File
@@ -1,17 +1,31 @@
namespace zero.Core.Entities
using zero.Core.Attributes;
namespace zero.Core.Entities
{
/// <summary>
/// A list item can consist of unlimited properties and be rendered as you wish
/// The backoffice rendering is done by an IRenderer
/// </summary>
public class SpaceContent : ZeroEntity, IAppAwareEntity, IZeroDbConventions
public class SpaceContent : ZeroEntity, ISpaceContent
{
/// <summary>
/// Associated space
/// </summary>
/// <inheritdoc />
public string SpaceAlias { get; set; }
/// <inheritdoc />
public string AppId { get; set; }
}
/// <summary>
/// A list item can consist of unlimited properties and be rendered as you wish
/// The backoffice rendering is done by an IRenderer
/// </summary>
[Collection("SpaceContents")]
public interface ISpaceContent : IZeroEntity, IAppAwareEntity, IZeroDbConventions
{
/// <summary>
/// Associated space
/// </summary>
string SpaceAlias { get; set; }
}
}
+22
View File
@@ -0,0 +1,22 @@
export default {
alias: 'space.social',
fields: [
{
field: 'twitter',
display: 'text',
label: 'Twitter'
},
{
field: 'facebook',
display: 'text',
label: 'Facebook'
},
{
field: 'youtube',
display: 'text',
label: 'YouTube'
},
]
};
+24
View File
@@ -0,0 +1,24 @@
export default {
alias: 'space.team',
fields: [
{
field: 'name',
display: 'text',
label: 'Name',
required: true
},
{
field: 'position',
display: 'text',
label: 'Position'
},
{
field: 'email',
display: 'text',
label: 'Email',
required: true
}
]
};
+6 -5
View File
@@ -27,7 +27,7 @@
default: () => [ 'uiProperty' ]
},
route: {
type: String,
type: [String, Object],
default: null
}
},
@@ -173,10 +173,11 @@
if (response.model && this.route && this.$route.name !== this.route)
{
this.$router.replace({
name: this.route,
params: { id: response.model.id }
});
let routeObj = typeof this.route === 'object' ? this.route : { name: this.route };
routeObj.params = routeObj.params || {};
routeObj.params.id = response.model.id;
this.$router.replace(routeObj);
}
}
else
+1 -1
View File
@@ -22,7 +22,7 @@
</slot>
<slot name="settings-properties"></slot>
</div>
<div class="ui-box">
<div class="ui-box" v-if="value.id">
<slot name="infos">
<ui-property v-if="value.id" label="@ui.id" :is-text="true">
{{value.id}}
+10
View File
@@ -20,6 +20,16 @@ if (section)
component: () => import('zero/pages/' + alias + '/spaces')
});
routes.push({
path: ':alias/create',
props: true,
name: 'space-create',
component: () => import('zero/pages/' + alias + '/spaces'),
meta: {
create: true
}
});
//routes.push({
// path: 'list/:id',
// props: true,
+2 -2
View File
@@ -1,7 +1,7 @@
<template>
<div class="spaces">
<div class="app-tree spaces-tree" v-resizable="resizable">
<ui-header-bar title="Spaces" />
<ui-header-bar title="@space.list" />
<div class="spaces-tree-items">
<div v-for="item in spaces" class="spaces-tree-item" :class="getClasses(item)">
<router-link :to="{ name: 'space', params: { alias: item.alias } }" class="spaces-tree-item-link">
@@ -113,7 +113,7 @@
this.space = _find(this.spaces, space => space.alias === this.$route.params.alias);
if (this.space.view === 'editor' || this.$route.params.id)
if (this.space.view === 'editor' || this.$route.params.id || this.$route.meta.create)
{
this.component = SpaceEditor;
}
+25 -50
View File
@@ -1,15 +1,7 @@
<template>
<ui-form ref="form" class="space-editor" v-slot="form" @submit="onSubmit" @load="onLoad">
<ui-header-bar :back-button="isList" :title="title" title-empty="Space">
<ui-dropdown v-if="isList && !disabled" align="right">
<template v-slot:button>
<ui-button type="white" label="@ui.actions" caret="down" />
</template>
<ui-dropdown-separator />
</ui-dropdown>
<ui-button :submit="true" label="@ui.save" :state="form.state" v-if="!disabled" />
</ui-header-bar>
<ui-editor v-if="renderer" :config="renderer" v-model="model" />
<ui-form ref="form" class="space-editor" v-slot="form" @submit="onSubmit" @load="onLoad" :route="route">
<ui-form-header v-model="model" title="@space.name" :disabled="disabled" :is-create="!id" :state="form.state" :can-delete="meta.canDelete" @delete="onDelete" />
<ui-editor v-if="renderer" :config="renderer" v-model="model" :meta="meta" />
</ui-form>
</template>
@@ -26,15 +18,24 @@
data: () => ({
disabled: false,
renderer: {},
model: null,
fullModel: null
renderer: null,
meta: {},
route: null, //zero.alias.sections.settings + '-' + zero.alias.settings.countries + '-edit',
model: { name: null }
}),
computed: {
id()
{
return this.$route.params.id;
},
alias()
{
return this.$route.params.alias;
},
isList()
{
return !!this.$route.params.id;
return !!this.id;
},
title()
{
@@ -42,59 +43,33 @@
}
},
beforeRouteLeave(to, from, next)
{
this.$refs.form.beforeRouteLeave(to, from, next);
},
methods: {
onLoad(form)
{
form.load(SpacesApi.getContent(this.$route.params.alias, this.$route.params.id)).then(response =>
form.load(SpacesApi.getContent(this.alias, this.id)).then(response =>
{
this.renderer = response.config;
this.model = response.model;
this.fullModel = response;
this.disabled = !response.meta.canEdit;
this.renderer = 'space.' + response.entity.spaceAlias;
this.meta = response.meta;
this.model = response.entity;
this.route = { name: 'space-item', params: { alias: this.alias } };
});
},
onSubmit(form)
{
this.fullModel.model = this.model;
form.handle(SpacesApi.save(this.fullModel)).then(response =>
{
console.info(response);
});
form.handle(SpacesApi.save(this.model));
},
onDelete(item, opts)
{
opts.hide();
Overlay.confirmDelete().then((opts) =>
{
opts.state('loading');
SpacesApi.delete(this.$route.params.alias, this.$route.params.id).then(response =>
{
if (response.success)
{
opts.state('success');
opts.hide();
this.$router.go(-1);
// TODO show message
}
else
{
opts.errors(response.errors);
}
});
});
}
this.$refs.form.onDelete(SpacesApi.delete.bind(this, this.alias, this.id));
}
}
}
</script>
+10 -2
View File
@@ -2,7 +2,7 @@
<div class="list">
<ui-header-bar :title="space.name" title-empty="List">
<ui-table-filter v-model="tableConfig" />
<ui-button label="Add item" icon="fth-plus" />
<ui-button label="@ui.add" icon="fth-plus" @click="add" />
</ui-header-bar>
<div class="ui-blank-box">
<ui-table v-model="tableConfig" />
@@ -61,8 +61,16 @@
}
}
};
}
},
add()
{
this.$router.push({
name: 'space-create',
params: { alias: this.space.alias }
});
}
}
}
</script>
+2 -2
View File
@@ -102,8 +102,8 @@
:root
{
// accent colors
--color-primary: #fdd330; // #fdd330; //linear-gradient(75deg, #48da9c, #3eb8ad); //#484c5b; // #292b2c; // #00aea2
--color-primary-fg: #191e25;
--color-primary: #18a471; // #fdd330; //linear-gradient(75deg, #48da9c, #3eb8ad); //#484c5b; // #292b2c; // #00aea2
--color-primary-fg: #fff;
--color-primary-low: rgba(229, 225, 221, 0.3);
--color-primary-two: #e5e4df;
--color-primary-line: #e0dfda;
+25 -25
View File
@@ -1,12 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using zero.Core.Api;
using zero.Core.Entities;
using zero.Core.Extensions;
using zero.Core.Identity;
using zero.Web.Models;
namespace zero.Web.Controllers
{
@@ -14,12 +14,10 @@ namespace zero.Web.Controllers
public class SpacesController : BackofficeController
{
ISpacesApi Api;
IAuthenticationApi AuthenticationApi;
public SpacesController(ISpacesApi api, IAuthenticationApi authenticationApi)
public SpacesController(ISpacesApi api)
{
Api = api;
AuthenticationApi = authenticationApi;
}
@@ -50,7 +48,7 @@ namespace zero.Web.Controllers
/// <summary>
/// Get list items in a space
/// </summary>
public async Task<IActionResult> GetList([FromQuery] string alias, [FromQuery] ListQuery<SpaceContent> query = null)
public async Task<IActionResult> GetList([FromQuery] string alias, [FromQuery] ListQuery<ISpaceContent> query = null)
{
if (!CanReadSpace(alias))
{
@@ -64,44 +62,46 @@ namespace zero.Web.Controllers
/// <summary>
/// Get list items in a space
/// </summary>
public IActionResult GetContent([FromQuery] string alias, [FromQuery] string contentId = null)
public async Task<IActionResult> GetContent([FromQuery] string alias, [FromQuery] string contentId = null)
{
if (!CanReadSpace(alias))
{
return new StatusCodeResult(403);
}
//TeamMember model = new TeamMember();
Space space = Api.GetByAlias(alias);
ISpaceContent model;
//if (!contentId.IsNullOrEmpty())
//{
// model = await Api.GetItem<TeamMember>(alias, contentId);
//}
JsonSerializerSettings settings = JsonConvert.DefaultSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
return Json(new SpaceContentEditModel()
if (space == null)
{
//Id = model.Id,
Alias = alias,
//Model = model,
//Config = Api.GetEditorConfig(alias)
}, settings);
return new StatusCodeResult(404);
}
if (space.View != SpaceView.List || !contentId.IsNullOrEmpty())
{
model = await Api.GetItem(alias, contentId);
}
else
{
model = Activator.CreateInstance(space.Type) as SpaceContent;
model.SpaceAlias = space.Alias;
}
return Edit(model);
}
/// <summary>
/// Save content item
/// </summary>
public async Task<IActionResult> Save([FromBody] SpaceContentEditModel model)
public async Task<IActionResult> Save([FromBody] ISpaceContent model)
{
if (!CanWriteSpace(model.Alias))
if (!CanWriteSpace(model.SpaceAlias))
{
return new StatusCodeResult(403);
}
return Json(await Api.Save(model.Alias, model.Model));
return Json(await Api.Save(model.SpaceAlias, model));
}
@@ -493,6 +493,11 @@
}
},
"space": {
"list": "Spaces",
"name": "Space"
},
"_test": {
"fields": {
"name": "Name",