mirror of
https://github.com/documize/community.git
synced 2025-07-18 20:59:43 +02:00
bulk data load methods for space and document views
This commit is contained in:
parent
bc72db711f
commit
318abef710
9 changed files with 315 additions and 29 deletions
18
README.md
18
README.md
|
@ -1,15 +1,19 @@
|
|||
# Documize Community Edition
|
||||
|
||||
## The mission
|
||||
|
||||
To bring software development inspired features to the world of documenting -- refactoring, importing, testing, linting, metrics, PRs, versioning....
|
||||
|
||||
## What is it?
|
||||
|
||||
Documize is an intelligent document environment (IDE) for creating, securing and sharing documents -- everything yoyu need in one place.
|
||||
Documize is an intelligent document environment (IDE) for creating, securing and sharing documents -- everything you need in one place.
|
||||
|
||||
## Why should I care?
|
||||
|
||||
Because maybe like us you are tired of:
|
||||
Because maybe like us, you might be tired of:
|
||||
|
||||
* juggling WYSIWYG editors, wiki software and various document related solutions
|
||||
* playing email tennis with document versions, contributions and feedback
|
||||
* playing document related email tennis with contributions, versions and feedback
|
||||
* sharing not-so-secure folders with external participants
|
||||
|
||||
Sound familiar? Read on.
|
||||
|
@ -18,10 +22,10 @@ Sound familiar? Read on.
|
|||
|
||||
Anyone who wants a single place for any kind of document.
|
||||
|
||||
Anyone who wants to loop in external participants without leaking information.
|
||||
Anyone who wants to loop in external participants complete security.
|
||||
|
||||
Anyone who wishes documentation and knowledge capture worked like agile software development.
|
||||
|
||||
|
||||
## What's different about Documize?
|
||||
|
||||
Sane organization through personal, team and public spaces.
|
||||
|
@ -83,10 +87,6 @@ Documize is compatible with Auth0 identity as a service.
|
|||
|
||||
Open Source Identity and Access Management
|
||||
|
||||
## The mission
|
||||
|
||||
To bring software development inspired features to the world of documenting -- refactoring, testing, linting, metrics, PRs, versioning....
|
||||
|
||||
## The legal bit at the end
|
||||
|
||||
<https://documize.com>
|
||||
|
|
|
@ -112,6 +112,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
h.Runtime.Log.Error("get space categories visible to user failed", err)
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
@ -460,3 +461,66 @@ func (h *Handler) GetSpaceCategoryMembers(w http.ResponseWriter, r *http.Request
|
|||
|
||||
response.WriteJSON(w, cat)
|
||||
}
|
||||
|
||||
// FetchSpaceData returns:
|
||||
// 1. categories that user can see for given space
|
||||
// 2. summary data for each category
|
||||
// 3. category viewing membership records
|
||||
func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {
|
||||
method := "category.FetchSpaceData"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
spaceID := request.Param(r, "spaceID")
|
||||
if len(spaceID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "spaceID")
|
||||
return
|
||||
}
|
||||
|
||||
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner, pm.SpaceView)
|
||||
if !ok {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
fetch := category.FetchSpaceModel{}
|
||||
|
||||
// get space categories visible to user
|
||||
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
h.Runtime.Log.Error("get space categories visible to user failed", err)
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
if len(cat) == 0 {
|
||||
cat = []category.Category{}
|
||||
}
|
||||
|
||||
// summary of space category usage
|
||||
summary, err := h.Store.Category.GetSpaceCategorySummary(ctx, spaceID)
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error("get space category summary failed", err)
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
if len(summary) == 0 {
|
||||
summary = []category.SummaryModel{}
|
||||
}
|
||||
|
||||
// get category membership records
|
||||
member, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
h.Runtime.Log.Error("get document category membership for space", err)
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(member) == 0 {
|
||||
member = []category.Member{}
|
||||
}
|
||||
|
||||
fetch.Category = cat
|
||||
fetch.Summary = summary
|
||||
fetch.Membership = member
|
||||
|
||||
response.WriteJSON(w, fetch)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,9 @@ import (
|
|||
"github.com/documize/community/model/audit"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/documize/community/model/link"
|
||||
pm "github.com/documize/community/model/permission"
|
||||
"github.com/documize/community/model/search"
|
||||
"github.com/documize/community/model/space"
|
||||
)
|
||||
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
|
@ -371,3 +373,104 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
response.WriteJSON(w, results)
|
||||
}
|
||||
|
||||
// FetchDocumentData returns all document data in single API call.
|
||||
func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
||||
method := "document.FetchDocumentData"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
id := request.Param(r, "documentID")
|
||||
if len(id) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
// document
|
||||
document, err := h.Store.Document.Get(ctx, id)
|
||||
if err == sql.ErrNoRows {
|
||||
response.WriteNotFoundError(w, method, id)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !permission.CanViewSpaceDocument(ctx, *h.Store, document.LabelID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// permissions
|
||||
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, document.LabelID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
if len(perms) == 0 {
|
||||
perms = []pm.Permission{}
|
||||
}
|
||||
|
||||
record := pm.DecodeUserPermissions(perms)
|
||||
|
||||
// links
|
||||
l, err := h.Store.Link.GetDocumentOutboundLinks(ctx, id)
|
||||
if len(l) == 0 {
|
||||
l = []link.Link{}
|
||||
}
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// spaces
|
||||
sp, err := h.Store.Space.GetViewable(ctx)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
if len(sp) == 0 {
|
||||
sp = []space.Space{}
|
||||
}
|
||||
|
||||
data := documentData{}
|
||||
data.Document = document
|
||||
data.Permissions = record
|
||||
data.Links = l
|
||||
data.Spaces = sp
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: document.LabelID,
|
||||
SourceID: document.RefID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeDocumentView)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteJSON(w, data)
|
||||
}
|
||||
|
||||
// documentData represents all data associated for a single document.
|
||||
// Used by FetchDocumentData() bulk data load call.
|
||||
type documentData struct {
|
||||
Document doc.Document `json:"document"`
|
||||
Permissions pm.Record `json:"permissions"`
|
||||
Spaces []space.Space `json:"folders"`
|
||||
Links []link.Link `json:"link"`
|
||||
}
|
||||
|
|
|
@ -24,22 +24,31 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
|||
this.set('documentId', this.paramsFor('document').document_id);
|
||||
|
||||
return new Ember.RSVP.Promise((resolve) => {
|
||||
this.get('documentService').getDocument(this.get('documentId')).then((document) => {
|
||||
this.set('document', document);
|
||||
|
||||
this.get('folderService').getAll().then((folders) => {
|
||||
this.set('folders', folders);
|
||||
|
||||
this.get('folderService').getFolder(this.get('folderId')).then((folder) => {
|
||||
this.set('folder', folder);
|
||||
|
||||
this.get('folderService').setCurrentFolder(folder).then(() => {
|
||||
this.set('permissions', this.get('folderService').get('permissions'));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
this.get('documentService').fetchDocumentData(this.get('documentId')).then((data) => {
|
||||
this.set('document', data.document);
|
||||
this.set('folders', data.folders);
|
||||
this.set('folder', data.folder);
|
||||
this.set('permissions', data.permissions);
|
||||
this.set('links', data.links);
|
||||
resolve();
|
||||
});
|
||||
|
||||
// this.get('documentService').getDocument(this.get('documentId')).then((document) => {
|
||||
// this.set('document', document);
|
||||
|
||||
// this.get('folderService').getAll().then((folders) => {
|
||||
// this.set('folders', folders);
|
||||
|
||||
// this.get('folderService').getFolder(this.get('folderId')).then((folder) => {
|
||||
// this.set('folder', folder);
|
||||
|
||||
// this.get('folderService').setCurrentFolder(folder).then(() => {
|
||||
// this.set('permissions', this.get('folderService').get('permissions'));
|
||||
// resolve();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -50,7 +59,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
|||
document: this.get('document'),
|
||||
page: this.get('pageId'),
|
||||
permissions: this.get('permissions'),
|
||||
links: this.get('linkService').getDocumentLinks(this.get('documentId')),
|
||||
links: this.get('links'),
|
||||
sections: this.get('sectionService').getAll()
|
||||
});
|
||||
},
|
||||
|
|
|
@ -15,6 +15,18 @@ import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-rout
|
|||
export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
||||
categoryService: Ember.inject.service('category'),
|
||||
|
||||
beforeModel() {
|
||||
return new Ember.RSVP.Promise((resolve) => {
|
||||
this.get('categoryService').fetchSpaceData(this.modelFor('folder').folder.get('id')).then((data) => {
|
||||
this.set('categories', data.category);
|
||||
this.set('categorySummary', data.summary);
|
||||
this.set('categoryMembers', data.membership);
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
model() {
|
||||
this.get('browser').setTitle(this.modelFor('folder').folder.get('name'));
|
||||
|
||||
|
@ -25,14 +37,25 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
|||
documents: this.modelFor('folder').documents,
|
||||
templates: this.modelFor('folder').templates,
|
||||
showStartDocument: false,
|
||||
categories: this.get('categoryService').getUserVisible(this.modelFor('folder').folder.get('id')),
|
||||
categorySummary: this.get('categoryService').getSummary(this.modelFor('folder').folder.get('id')),
|
||||
categoryMembers: this.get('categoryService').getSpaceCategoryMembership(this.modelFor('folder').folder.get('id')),
|
||||
rootDocCount: 0
|
||||
rootDocCount: 0,
|
||||
categories: this.get('categories'),
|
||||
categorySummary: this.get('categorySummary'),
|
||||
categoryMembers: this.get('categoryMembers'),
|
||||
// categories: this.get('categoryService').getUserVisible(this.modelFor('folder').folder.get('id')),
|
||||
// categorySummary: this.get('categoryService').getSummary(this.modelFor('folder').folder.get('id')),
|
||||
// categoryMembers: this.get('categoryService').getSpaceCategoryMembership(this.modelFor('folder').folder.get('id')),
|
||||
});
|
||||
},
|
||||
|
||||
afterModel(model, transition) { // eslint-disable-line no-unused-vars
|
||||
// model.folder = this.modelFor('folder').folder;
|
||||
// model.permissions = this.modelFor('folder').permissions;
|
||||
// model.folders = this.modelFor('folder').folders;
|
||||
// model.documents = this.modelFor('folder').documents;
|
||||
// model.templates = this.modelFor('folder').templates;
|
||||
// model.showStartDocument = false;
|
||||
// model.rootDocCount = 0;
|
||||
|
||||
let docs = model.documents;
|
||||
let categoryMembers = model.categoryMembers;
|
||||
let rootDocCount = 0;
|
||||
|
|
|
@ -151,5 +151,34 @@ export default BaseService.extend({
|
|||
}).then((response) => {
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
||||
// fetchXXX represents UI specific bulk data loading designed to
|
||||
// reduce network traffic and boost app performance.
|
||||
// This method that returns:
|
||||
// 1. getUserVisible()
|
||||
// 2. getSummary()
|
||||
// 3. getSpaceCategoryMembership()
|
||||
fetchSpaceData(spaceId) {
|
||||
return this.get('ajax').request(`fetch/category/space/${spaceId}`, {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
let data = {
|
||||
category: [],
|
||||
membership: [],
|
||||
summary: []
|
||||
};
|
||||
|
||||
let cats = response.category.map((obj) => {
|
||||
let data = this.get('store').normalize('category', obj);
|
||||
return this.get('store').push(data);
|
||||
});
|
||||
|
||||
data.category = cats;
|
||||
data.membership = response.membership;
|
||||
data.summary = response.summary;
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ const {
|
|||
|
||||
export default Ember.Service.extend({
|
||||
sessionService: service('session'),
|
||||
folderService: service('folder'),
|
||||
ajax: service(),
|
||||
store: service(),
|
||||
|
||||
|
@ -308,6 +309,51 @@ export default Ember.Service.extend({
|
|||
let data = this.get('store').normalize('page', response);
|
||||
return this.get('store').push(data);
|
||||
});
|
||||
},
|
||||
|
||||
//**************************************************
|
||||
// Fetch bulk data
|
||||
//**************************************************
|
||||
|
||||
// fetchXXX represents UI specific bulk data loading designed to
|
||||
// reduce network traffic and boost app performance.
|
||||
// This method that returns:
|
||||
// 1. getUserVisible()
|
||||
// 2. getSummary()
|
||||
// 3. getSpaceCategoryMembership()
|
||||
fetchDocumentData(documentId) {
|
||||
return this.get('ajax').request(`fetch/document/${documentId}`, {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
let data = {
|
||||
document: {},
|
||||
permissions: {},
|
||||
folders: [],
|
||||
folder: {},
|
||||
links: [],
|
||||
};
|
||||
|
||||
let doc = this.get('store').normalize('document', response.document);
|
||||
doc = this.get('store').push(doc);
|
||||
|
||||
let perms = this.get('store').normalize('space-permission', response);
|
||||
perms= this.get('store').push(perms);
|
||||
this.get('folderService').set('permissions', perms);
|
||||
|
||||
let folders = response.folders.map((obj) => {
|
||||
let data = this.get('store').normalize('folder', obj);
|
||||
return this.get('store').push(data);
|
||||
});
|
||||
|
||||
data.document = doc;
|
||||
data.permissions = perms;
|
||||
data.folders = folders;
|
||||
data.folder = folders.findBy('id', doc.get('folderId'));
|
||||
data.links = response.links;
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -36,3 +36,11 @@ type SummaryModel struct {
|
|||
CategoryID string `json:"categoryId"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
// FetchSpaceModel represents categories, summary and membership in a single payload.
|
||||
// Designed to speed up front-end app.
|
||||
type FetchSpaceModel struct {
|
||||
Category []Category `json:"category"`
|
||||
Summary []SummaryModel `json:"summary"`
|
||||
Membership []Member `json:"membership"`
|
||||
}
|
||||
|
|
|
@ -179,6 +179,10 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
|
|||
Add(rt, RoutePrefixPrivate, "pin/{userID}/sequence", []string{"POST", "OPTIONS"}, nil, pin.UpdatePinSequence)
|
||||
Add(rt, RoutePrefixPrivate, "pin/{userID}/{pinID}", []string{"DELETE", "OPTIONS"}, nil, pin.DeleteUserPin)
|
||||
|
||||
// fetch methods exist to speed up UI rendering by returning data in bulk
|
||||
Add(rt, RoutePrefixPrivate, "fetch/category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.FetchSpaceData)
|
||||
Add(rt, RoutePrefixPrivate, "fetch/document/{documentID}", []string{"GET", "OPTIONS"}, nil, document.FetchDocumentData)
|
||||
|
||||
Add(rt, RoutePrefixRoot, "robots.txt", []string{"GET", "OPTIONS"}, nil, meta.RobotsTxt)
|
||||
Add(rt, RoutePrefixRoot, "sitemap.xml", []string{"GET", "OPTIONS"}, nil, meta.Sitemap)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue