-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wip: implement basic cru(d) actions and add some helpful hooks
- Loading branch information
Showing
24 changed files
with
1,066 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Code Blocks | ||
|
||
```py live_py id=7cf4fb1c-8495-4600-bf67-23fb7bd29deb | ||
print('hello world') | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import api from './base'; | ||
import { AxiosPromise } from 'axios'; | ||
import { Access, Document, DocumentType } from './document'; | ||
import { GroupPermissionBase, UserPermissionBase } from './permission'; | ||
|
||
export interface RootGroupPermission { | ||
id: string; | ||
rootGroupPermissions: string; | ||
access: Access; | ||
} | ||
|
||
export interface DocumentRootBase { | ||
id: string; | ||
access: Access; | ||
} | ||
|
||
export interface DocumentRoot extends DocumentRootBase { | ||
userPermissions: UserPermissionBase[]; | ||
groupPermissions: GroupPermissionBase[]; | ||
documents: Document<DocumentType>[]; | ||
} | ||
|
||
export interface Config { | ||
access?: Access; | ||
userPermissions: Omit<UserPermissionBase, 'id'>[]; | ||
groupPermissions: Omit<GroupPermissionBase, 'id'>[]; | ||
} | ||
|
||
export function find(id: string, signal: AbortSignal): AxiosPromise<DocumentRoot> { | ||
return api.get(`/documentRoots/${id}`, { signal }); | ||
} | ||
|
||
export function create( | ||
id: string, | ||
data: Partial<Config> = {}, | ||
signal: AbortSignal | ||
): AxiosPromise<DocumentRoot> { | ||
return api.post(`/documentRoots/${id}`, data, { signal }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import api from './base'; | ||
import { AxiosPromise } from 'axios'; | ||
import { Access } from './document'; | ||
|
||
export interface GroupPermissionBase { | ||
id: string; | ||
groupId: string; | ||
access: Access; | ||
} | ||
|
||
export interface GroupPermission extends GroupPermissionBase { | ||
documentRootId: string; | ||
} | ||
|
||
export interface UserPermissionBase { | ||
id: string; | ||
userId: string; | ||
access: Access; | ||
} | ||
|
||
export interface UserPermission extends UserPermissionBase { | ||
documentRootId: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import api from './base'; | ||
import { AxiosPromise } from 'axios'; | ||
import { Access } from './document'; | ||
|
||
export interface StudentGroup { | ||
id: string; | ||
name: string; | ||
description: string; | ||
userIds: string[]; | ||
|
||
parentId?: string; | ||
|
||
createdAt: string; | ||
updatedAt: string; | ||
} | ||
|
||
export function all(signal: AbortSignal): AxiosPromise<StudentGroup[]> { | ||
return api.get(`/studentGroups`, { signal }); | ||
} | ||
|
||
export function create(data: Partial<StudentGroup> = {}, signal: AbortSignal): AxiosPromise<StudentGroup> { | ||
return api.post(`/studentGroups`, data, { signal }); | ||
} | ||
|
||
export function update( | ||
id: string, | ||
data: Partial<StudentGroup> = {}, | ||
signal: AbortSignal | ||
): AxiosPromise<StudentGroup> { | ||
return api.put(`/studentGroups/${id}`, { data }, { signal }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import React, { useId } from 'react'; | ||
import { Access, Document, DocumentType } from '../api/document'; | ||
import { rootStore } from '../stores/rootStore'; | ||
import { ApiState } from '../stores/iStore'; | ||
import DocumentRoot, { TypeMeta } from '../models/DocumentRoot'; | ||
|
||
export const useDocumentRoot = <Type extends DocumentType>(id: string | undefined, meta: TypeMeta<Type>) => { | ||
const defaultRootDocId = useId(); | ||
const defaultDocId = useId(); | ||
const [dummyDocumentRoot] = React.useState<DocumentRoot<Type>>( | ||
new DocumentRoot( | ||
{ id: id || defaultRootDocId, access: Access.RW }, | ||
meta, | ||
rootStore.documentRootStore, | ||
true | ||
) | ||
); | ||
|
||
/** initial load */ | ||
React.useEffect(() => { | ||
const rootDoc = rootStore.documentRootStore.find(dummyDocumentRoot.id); | ||
if (rootDoc || rootStore.documentRootStore.apiStateFor(`load-${dummyDocumentRoot.id}`) === ApiState.LOADING) { | ||
return; | ||
} | ||
if (dummyDocumentRoot.isDummy) { | ||
rootStore.documentRootStore.addDocumentRoot(dummyDocumentRoot); | ||
/** add default document when there are no mainDocs */ | ||
if (dummyDocumentRoot.mainDocuments.length === 0) { | ||
const now = new Date().toISOString(); | ||
rootStore.documentStore.addToStore({ | ||
type: meta.type, | ||
data: meta.defaultData, | ||
authorId: rootStore.userStore.current?.id || '', | ||
createdAt: now, | ||
updatedAt: now, | ||
documentRootId: dummyDocumentRoot.id, | ||
id: defaultDocId, | ||
parentId: null | ||
} satisfies Document<Type>); | ||
} | ||
if (dummyDocumentRoot.id === defaultRootDocId) { | ||
/** no according document in the backend can be expected - skip */ | ||
return; | ||
} | ||
} | ||
|
||
/** | ||
* load the documentRoot and it's documents from the api. | ||
*/ | ||
rootStore.documentRootStore | ||
.load(id, meta) | ||
.then((docRoot) => { | ||
if (!docRoot) { | ||
return rootStore.documentRootStore.create(id, meta, {}).then((docRoot) => { | ||
return docRoot; | ||
}); | ||
} | ||
return docRoot; | ||
}) | ||
.then((docRoot) => { | ||
if (docRoot) { | ||
if (docRoot.mainDocuments.length === 0 && rootStore.userStore.current) { | ||
rootStore.documentStore.create({ | ||
documentRootId: docRoot.id, | ||
authorId: rootStore.userStore.current.id, | ||
type: docRoot.type, | ||
data: meta.defaultData | ||
}); | ||
} | ||
} | ||
}) | ||
.catch((err) => { | ||
console.log('err loading', err); | ||
}); | ||
}, [meta, id]); | ||
|
||
return dummyDocumentRoot.id; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { action, computed, observable } from 'mobx'; | ||
import { DocumentRootBase as DocumentRootProps } from '../api/documentRoot'; | ||
import { DocumentRootStore } from '../stores/DocumentRootStore'; | ||
import { Access, Document, DocumentType, TypeDataMapping } from '../api/document'; | ||
import { highestAccess } from './helpers/accessPolicy'; | ||
import { TypeModelMapping } from '../stores/DocumentStore'; | ||
|
||
export abstract class TypeMeta<T extends DocumentType> { | ||
type: T; | ||
access?: Access; | ||
constructor(type: T, access?: Access) { | ||
this.type = type; | ||
this.access = access; | ||
} | ||
abstract get defaultData(): TypeDataMapping[T]; | ||
} | ||
|
||
class DocumentRoot<T extends DocumentType> { | ||
readonly store: DocumentRootStore; | ||
readonly id: string; | ||
readonly meta: TypeMeta<T>; | ||
/** | ||
* dummy document roots are used to create new documents, which should not be | ||
* persisted to the api. | ||
* This is useful to support interactive behavior even for not logged in users or | ||
* in offline mode. | ||
*/ | ||
readonly _isDummy: boolean; | ||
|
||
@observable accessor _access: Access; | ||
|
||
constructor( | ||
props: DocumentRootProps, | ||
meta: TypeMeta<T>, | ||
store: DocumentRootStore, | ||
isDummy: boolean = false | ||
) { | ||
this.store = store; | ||
this.meta = meta; | ||
this.id = props.id; | ||
this._access = props.access; | ||
this._isDummy = isDummy; | ||
} | ||
|
||
@computed | ||
get isDummy() { | ||
return this._isDummy || !this.store.root.sessionStore.isLoggedIn; | ||
} | ||
|
||
get type() { | ||
return this.meta.type; | ||
} | ||
|
||
get access() { | ||
if (this.meta.access) { | ||
return this.meta.access; | ||
} | ||
return this.meta.access || this._access; | ||
} | ||
|
||
@computed | ||
get permissions() { | ||
return this.store.usersPermissions(this.id); | ||
} | ||
|
||
@computed | ||
get permission() { | ||
return highestAccess(new Set([...this.permissions.map((p) => p.access)])); | ||
} | ||
|
||
get documents() { | ||
return this.store.root.documentStore.findByDocumentRoot(this.id); | ||
} | ||
|
||
/** | ||
* All documents which | ||
* - **don't have a parent** | ||
* - having the **same type** as this document root | ||
* | ||
* @returns All main documents, **ordered by creation date**, oldest first. | ||
*/ | ||
@computed | ||
get mainDocuments(): TypeModelMapping[T][] { | ||
return this.documents | ||
.filter((d) => d.isRoot && d.type === this.type) | ||
.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) as TypeModelMapping[T][]; | ||
} | ||
|
||
@computed | ||
get firstMainDocument(): TypeModelMapping[T] | undefined { | ||
return this.mainDocuments[0]; | ||
} | ||
} | ||
|
||
export default DocumentRoot; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { computed, observable } from 'mobx'; | ||
import { Access } from '../api/document'; | ||
import PermissionStore from '../stores/PermissionStore'; | ||
import { GroupPermission } from '../api/permission'; | ||
import User from './User'; | ||
|
||
class PermissionGroup { | ||
readonly store: PermissionStore; | ||
|
||
readonly id: string; | ||
readonly documentRootId: string; | ||
readonly groupId: string; | ||
|
||
@observable accessor access: Access; | ||
|
||
constructor(props: GroupPermission, store: PermissionStore) { | ||
this.store = store; | ||
this.id = props.id; | ||
this.groupId = props.groupId; | ||
} | ||
|
||
@computed | ||
get groups() { | ||
return this.store.studentGroups.filter((g) => g.id === this.groupId); | ||
} | ||
|
||
@computed | ||
get userIds() { | ||
return new Set(this.groups.flatMap((g) => [...g.userIds])); | ||
} | ||
|
||
@computed | ||
get users() { | ||
return this.store.root.userStore.users.filter((u) => this.userIds.has(u.id)); | ||
} | ||
|
||
isAffectingUser(user: User) { | ||
return this.userIds.has(user.id); | ||
} | ||
} | ||
|
||
export default PermissionGroup; |
Oops, something went wrong.