Skip to content

Commit

Permalink
Merge pull request #1556 from zazuko/dim-contributor
Browse files Browse the repository at this point in the history
undefined
  • Loading branch information
tpluscode authored Nov 6, 2024
2 parents e6a43dc + fd6bba2 commit f0f22a9
Show file tree
Hide file tree
Showing 13 changed files with 394 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .changeset/big-humans-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-creator/shared-dimensions-api": patch
---

Added `Contributors` to shared dimension
1 change: 1 addition & 0 deletions apis/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The API is secured with a JWT middleware. To configure set these environment var
In the local environment it is possible to authorize requests with HTTP headers serving as a testing backdoor

- `X-USER` - the user id
- `X-EMAIL` - the user email
- `X-PERMISSION` - authorized permissions (multiple values allowed)

### Debugging
Expand Down
5 changes: 5 additions & 0 deletions apis/core/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ declare module '@hydrofoil/labyrinth' {
export interface User {
sub: string
name: string
email?: string
permissions: string[]
}
}
Expand Down Expand Up @@ -46,11 +47,15 @@ function devAuthHandler(req: Request, res: Response, next: NextFunction) {

if (sub) {
const permissionHeader = req.headers['x-permission']
const emailHeader = req.headers['x-email']

const permissions = typeof permissionHeader === 'string' ? permissionHeader.split(',').map(s => s.trim()) : permissionHeader || []
const email = typeof emailHeader === 'string' ? emailHeader : (emailHeader || []).shift()

req.user = {
sub,
name: sub,
email,
permissions,
}

Expand Down
2 changes: 1 addition & 1 deletion apis/shared-dimensions/hydra/index.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ md:SharedDimension
code:implementedBy
[
a code:EcmaScript ;
code:link <file:handlers/resource#put> ;
code:link <file:handlers/shared-dimension#put> ;
] ;
] ;
.
Expand Down
26 changes: 24 additions & 2 deletions apis/shared-dimensions/lib/domain/shared-dimension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ import * as queries from './shared-dimension/queries'

export { importDimension } from './shared-dimension/import'

interface Contributor {
name: string
email?: string
}

interface CreateSharedDimension {
resource: GraphPointer<NamedNode>
store: SharedDimensionsStore
contributor: Contributor
}

export async function create({ resource, store }: CreateSharedDimension): Promise<GraphPointer> {
export async function create({ resource, store, contributor }: CreateSharedDimension): Promise<GraphPointer> {
const identifier = resource.out(dcterms.identifier).value
if (!identifier) {
throw new DomainError('Missing dimension identifier')
Expand All @@ -40,6 +46,8 @@ export async function create({ resource, store }: CreateSharedDimension): Promis
.addOut(rdf.type, [hydra.Resource, schema.DefinedTermSet, meta.SharedDimension, md.SharedDimension])
.deleteOut(md.createAs)

setDefaultContributor(termSet, contributor)

await store.save(termSet)
return termSet
}
Expand Down Expand Up @@ -81,6 +89,7 @@ interface UpdateSharedDimension {
store: SharedDimensionsStore
shape: MultiPointer | undefined
queries?: typeof queries
contributor: Contributor
}

function removeSubgraph(pointer: GraphPointer, predicate?: Term) {
Expand All @@ -92,7 +101,7 @@ function removeSubgraph(pointer: GraphPointer, predicate?: Term) {
}
}

export async function update({ resource, store, shape, queries }: UpdateSharedDimension): Promise<GraphPointer> {
export async function update({ resource, store, shape, queries, contributor }: UpdateSharedDimension): Promise<GraphPointer> {
const ignoredProperties = shape
?.out(sh.ignoredProperties)
.list() || []
Expand All @@ -107,6 +116,8 @@ export async function update({ resource, store, shape, queries }: UpdateSharedDi
deletedProperties.delete(prop)
}

setDefaultContributor(resource, contributor)

await store.save(resource)
await queries?.deleteDynamicTerms({
dimension: resource.term,
Expand All @@ -116,6 +127,17 @@ export async function update({ resource, store, shape, queries }: UpdateSharedDi
return resource
}

function setDefaultContributor(termSet: GraphPointer, contributor: Contributor) {
if (termSet.out(dcterms.contributor).terms.length === 0) {
termSet.addOut(dcterms.contributor, contributorPtr => {
contributorPtr.addOut(schema.name, contributor.name)
if (contributor.email) {
contributorPtr.addOut(schema.email, contributor.email)
}
})
}
}

interface GetExportedDimension {
resource: NamedNode
store: SharedDimensionsStore
Expand Down
22 changes: 4 additions & 18 deletions apis/shared-dimensions/lib/handlers/resource.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import asyncMiddleware from 'middleware-async'
import { NO_CONTENT } from 'http-status'
import clownface, { GraphPointer } from 'clownface'
import clownface from 'clownface'
import { protectedResource } from '@hydrofoil/labyrinth/resource'
import { hydra } from '@tpluscode/rdf-ns-builders/strict'
import { store } from '../store'
import { cascadeDelete } from '../domain/resource'
import { shaclValidate } from '../middleware/shacl'
import shapes from '../shapes/index'
import { update } from '../domain/shared-dimension'
import { rewrite } from '../rewrite'
import * as queries from '../domain/shared-dimension/queries'

export const DELETE = protectedResource(asyncMiddleware(async (req, res) => {
await cascadeDelete({
Expand All @@ -22,18 +18,8 @@ export const DELETE = protectedResource(asyncMiddleware(async (req, res) => {
}))

export const put = protectedResource(shaclValidate, asyncMiddleware(async (req, res) => {
const hydraExpects = req.hydra.operation.out(hydra.expects).term
let shape: GraphPointer | undefined
if (hydraExpects?.termType === 'NamedNode') {
shape = await shapes.get(hydraExpects)?.(req)
}
const resource = rewrite(await req.resource())
await store().save(resource)

const dimension = await update({
resource: rewrite(await req.resource()),
store: store(),
shape,
queries,
})

return res.dataset(dimension.dataset)
return res.dataset(resource.dataset)
}))
26 changes: 23 additions & 3 deletions apis/shared-dimensions/lib/handlers/shared-dimension.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import asyncMiddleware from 'middleware-async'
import { protectedResource } from '@hydrofoil/labyrinth/resource'
import clownface from 'clownface'
import clownface, { GraphPointer } from 'clownface'
import { schema } from '@tpluscode/rdf-ns-builders/strict'
import cors from 'cors'
import { serializers } from '@rdfjs-elements/formats-pretty'
import error from 'http-errors'
import { md, meta } from '@cube-creator/core/namespace'
import * as ns from '@tpluscode/rdf-ns-builders'
import { oa } from '@tpluscode/rdf-ns-builders'
import { createTerm, getExportedDimension } from '../domain/shared-dimension'
import { hydra, oa } from '@tpluscode/rdf-ns-builders'
import { createTerm, getExportedDimension, update } from '../domain/shared-dimension'
import { store } from '../store'
import { shaclValidate } from '../middleware/shacl'
import { rewrite } from '../rewrite'
import shapes from '../shapes'
import * as queries from '../domain/shared-dimension/queries'

export const post = protectedResource(shaclValidate, asyncMiddleware(async (req, res) => {
const term = await createTerm({
Expand All @@ -27,6 +29,24 @@ export const post = protectedResource(shaclValidate, asyncMiddleware(async (req,
return res.dataset(term.dataset)
}))

export const put = protectedResource(shaclValidate, asyncMiddleware(async (req, res) => {
const hydraExpects = req.hydra.operation.out(hydra.expects).term
let shape: GraphPointer | undefined
if (hydraExpects?.termType === 'NamedNode') {
shape = await shapes.get(hydraExpects)?.(req)
}

const dimension = await update({
resource: rewrite(await req.resource()),
store: store(),
shape,
queries,
contributor: req.user!,
})

return res.dataset(dimension.dataset)
}))

export const getExport = protectedResource(cors({ exposedHeaders: 'content-disposition' }), asyncMiddleware(async (req, res, next) => {
if (!req.dataset) {
return next(new error.BadRequest())
Expand Down
1 change: 1 addition & 0 deletions apis/shared-dimensions/lib/handlers/shared-dimensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const postDirect = protectedResource(shaclValidate, asyncMiddleware(async (req,
const pointer = await create({
resource: rewrite(await req.resource()),
store: store(),
contributor: req.user!,
})

res.setHeader('Location', pointer.value)
Expand Down
22 changes: 22 additions & 0 deletions apis/shared-dimensions/lib/shapes/shared-dimension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,28 @@ const properties: Initializer<PropertyShape>[] = [{
}],
})],
},
}, {
name: 'Contributors',
description: 'Currently logged-in user will be used, if not set',
path: dcterms.contributor,
order: 50,
nodeKind: sh.BlankNode,
group: propertyGroup({
label: 'Contributors',
}),
node: {
property: [{
name: 'Name',
path: schema.name,
minCount: 1,
maxCount: 1,
}, {
name: 'Email',
path: schema.email,
minCount: 1,
maxCount: 1,
}],
},
}]

export const create = (): Initializer<NodeShape> => ({
Expand Down
14 changes: 14 additions & 0 deletions apis/shared-dimensions/lib/store/shapes.ttl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PREFIX dcterm: <http://purl.org/dc/terms/>
PREFIX time: <http://www.w3.org/2006/time#>
PREFIX qudt: <http://qudt.org/schema/qudt/>
PREFIX meta: <https://cube.link/meta/>
Expand Down Expand Up @@ -82,6 +83,19 @@ PREFIX sh: <http://www.w3.org/ns/shacl#>
sh:path sh:languageIn ;
] ;
] ;
],
[
sh:path dcterm:contributor ;
sh:node
[
sh:property
[
sh:path schema:name ;
] ,
[
sh:path schema:email ;
] ;
]
] ;
] .

Expand Down
Loading

0 comments on commit f0f22a9

Please sign in to comment.