Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(shared-dimension): contributors #1556

Merged
merged 4 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
export interface User {
sub: string
name: string
email?: string

Check warning on line 17 in apis/core/lib/auth.ts

View check run for this annotation

Codecov / codecov/patch

apis/core/lib/auth.ts#L17

Added line #L17 was not covered by tests
permissions: string[]
}
}
Expand Down Expand Up @@ -46,11 +47,15 @@

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

Check warning on line 51 in apis/core/lib/auth.ts

View check run for this annotation

Codecov / codecov/patch

apis/core/lib/auth.ts#L50-L51

Added lines #L50 - L51 were not covered by tests
const permissions = typeof permissionHeader === 'string' ? permissionHeader.split(',').map(s => s.trim()) : permissionHeader || []
const email = typeof emailHeader === 'string' ? emailHeader : (emailHeader || []).shift()

Check warning on line 53 in apis/core/lib/auth.ts

View check run for this annotation

Codecov / codecov/patch

apis/core/lib/auth.ts#L53

Added line #L53 was not covered by tests

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

Check warning on line 58 in apis/core/lib/auth.ts

View check run for this annotation

Codecov / codecov/patch

apis/core/lib/auth.ts#L58

Added line #L58 was not covered by tests
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> ;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was surprised to discover that the shared dimensions and hierarchies were both handled by the "resource" handler which was in fact specific to shared dimensions. I moved that to its own module and simplified the generic one

] ;
] ;
.
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'

Check warning on line 3 in apis/shared-dimensions/lib/handlers/resource.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/resource.ts#L3

Added line #L3 was not covered by tests
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 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)

Check warning on line 22 in apis/shared-dimensions/lib/handlers/resource.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/resource.ts#L21-L22

Added lines #L21 - L22 were not covered by tests

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

return res.dataset(dimension.dataset)
return res.dataset(resource.dataset)

Check warning on line 24 in apis/shared-dimensions/lib/handlers/resource.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/resource.ts#L24

Added line #L24 was not covered by tests
}))
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'

Check warning on line 3 in apis/shared-dimensions/lib/handlers/shared-dimension.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/shared-dimension.ts#L3

Added line #L3 was not covered by tests
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'

Check warning on line 11 in apis/shared-dimensions/lib/handlers/shared-dimension.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/shared-dimension.ts#L10-L11

Added lines #L10 - L11 were not covered by tests
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'

Check warning on line 16 in apis/shared-dimensions/lib/handlers/shared-dimension.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/shared-dimension.ts#L15-L16

Added lines #L15 - L16 were not covered by tests

export const post = protectedResource(shaclValidate, asyncMiddleware(async (req, res) => {
const term = await createTerm({
Expand All @@ -27,6 +29,24 @@
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)
}))

Check warning on line 49 in apis/shared-dimensions/lib/handlers/shared-dimension.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/shared-dimension.ts#L32-L49

Added lines #L32 - L49 were not covered by tests
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 pointer = await create({
resource: rewrite(await req.resource()),
store: store(),
contributor: req.user!,

Check warning on line 111 in apis/shared-dimensions/lib/handlers/shared-dimensions.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/shared-dimensions.ts#L111

Added line #L111 was not covered by tests
})

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
Loading