Skip to content
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
2 changes: 1 addition & 1 deletion api/doc/publications/post-req/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default {
properties: {
body:
jsonSchema(publicationSchema)
.pickProperties(['catalog', 'action', 'dataFairDataset', 'publicationSite', 'remoteDataset'])
.pickProperties(['catalog', 'action', 'dataFairDataset', 'publicationSite', 'remoteFolder', 'remoteResource'])
.removeId()
.appendTitle(' post')
.schema
Expand Down
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"#utils/*": "./src/misc/utils/*"
},
"devDependencies": {
"@data-fair/types-catalogs": "^0.4.0",
"@data-fair/types-catalogs": "^0.5.0",
"@types/memoizee": "^0.4.11",
"@types/multer": "^1.4.13",
"@types/resolve-path": "^1.4.3",
Expand Down
33 changes: 2 additions & 31 deletions api/src/catalogs/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ router.delete('/:id', async (req, res) => {
res.status(204).send()
})

// Get the list of remote resources from a catalog
// Explore the list of remote folders/resources from a catalog
router.get('/:id/resources', async (req, res) => {
const sessionState = await session.reqAuthenticated(req)
const catalog = await mongo.catalogs.findOne({ _id: req.params.id })
Expand All @@ -208,40 +208,11 @@ router.get('/:id/resources', async (req, res) => {

// Execute the plugin function
const plugin = await getPlugin(catalog.plugin)
if (!plugin.metadata.capabilities.includes('import')) throw httpError(501, 'Plugin does not support listing resources')
const datasets = await plugin.listResources({
const datasets = await plugin.list({
catalogConfig: catalog.config,
secrets: decipherSecrets(catalog.secrets, config.cipherPassword),
params: req.query as Record<string, any>
})

res.status(200).json(datasets)
})

// Get the list of remote datasets from a catalog
router.get('/:id/datasets', async (req, res) => {
const sessionState = await session.reqAuthenticated(req)
const catalog = await mongo.catalogs.findOne({ _id: req.params.id })
if (!catalog) throw httpError(404, 'Catalog not found')
assertAccountRole(sessionState, catalog.owner, 'admin')

// Execute the plugin function
const plugin = await getPlugin(catalog.plugin)
if (!plugin.metadata.capabilities.includes('publication')) throw httpError(501, 'Plugin does not support listing datasets')
// Validate params
if (req.query.q && typeof req.query.q !== 'string') throw httpError(400, 'Invalid query parameter "q", must be a string')
if (!['addAsResource', 'overwrite'].includes(req.query.mode as string)) {
throw httpError(400, 'Invalid query parameter "mode", must be "addAsResource" or "overwrite"')
}

const datasets = await plugin.listDatasets({
catalogConfig: catalog.config,
secrets: decipherSecrets(catalog.secrets, config.cipherPassword),
params: {
q: req.query.q,
mode: req.query.mode as 'addAsResource' | 'overwrite'
}
})

res.status(200).json(datasets)
})
2 changes: 2 additions & 0 deletions api/src/plugins/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ router.get('/:id', async (req, res) => {
version: pluginInfo.version,
configSchema: plugin.configSchema,
listFiltersSchema: plugin.listFiltersSchema,
importFiltersSchema: plugin.importFiltersSchema,
publicationFiltersSchema: plugin.publicationFiltersSchema,
importConfigSchema: plugin.importConfigSchema,
metadata: plugin.metadata
} as Plugin)
Expand Down
4 changes: 2 additions & 2 deletions api/src/plugins/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const installPlugin = async (req: Request & { file?: Express.Multer.File
const extractedPath = path.join(dir.path, 'package')

// install dependencies of the plugin
await execAsync('npm install', { cwd: extractedPath })
await execAsync('npm i --omit=dev --no-audit --no-fund', { cwd: extractedPath })

// generate plugin.json from package.json
const packageJson = await fs.readJson(path.join(extractedPath, 'package.json'))
Expand Down Expand Up @@ -126,7 +126,7 @@ export const getPlugin = async (pluginId: string): Promise<CatalogPlugin> => {
const plugin = (await import(pluginPath)).default

// For compatibility
if (plugin.list && !plugin.listResources) plugin.listResources = plugin.list
if (plugin.listResources && !plugin.list) plugin.list = plugin.listResources

return plugin
} catch (e: any) {
Expand Down
11 changes: 7 additions & 4 deletions api/src/publications/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ router.get('/', async (req, res) => {

const params = (await import('../../doc/publications/get-req/index.ts')).returnValid(req.query)
const sort = findUtils.sort(params.sort || 'lastPublicationDate:-1,created.date:-1')
const { skip, size } = findUtils.pagination(params)
const { skip, size } = findUtils.pagination(params, 1000)
const project = findUtils.project(params.select)
const query = findUtils.filterPermissions(params, sessionState)
const queryWithFilters = Object.assign(findUtils.query(params, { catalogId: 'catalog.id', dataFairDatasetId: 'dataFairDataset.id' }), query)
Expand All @@ -51,9 +51,12 @@ router.post('/', async (req, res) => {
})
if (existingPublication) throw httpError(409, 'Publication already exists for this dataset and catalog')

// In overwrite mode, check if they are already a publication with the same remoteDataset.id, and delete the link
if (body.action === 'overwrite' && body.remoteDataset?.id) {
await mongo.publications.deleteOne({ 'remoteDataset.id': body.remoteDataset?.id })
// In replace mode, check if they are already a publication with the same remote Folder/Resource id, and delete the link
if (body.action === 'replaceFolder' && body.remoteFolder?.id) {
await mongo.publications.deleteOne({ 'remoteFolder.id': body.remoteFolder?.id })
}
if (body.action === 'replaceResource' && body.remoteResource?.id) {
await mongo.publications.deleteOne({ 'remoteResource.id': body.remoteResource?.id })
}

// Check if the catalog exists
Expand Down
22 changes: 7 additions & 15 deletions api/types/plugin/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,12 @@ export default {
type: 'string',
description: 'The package description of the plugin (from package.json).'
},
version: {
type: 'string'
},
configSchema: {
type: 'object'
},
listFiltersSchema: {
type: 'object'
},
importConfigSchema: {
type: 'object'
},
metadata: {
$ref: 'https://github.com/data-fair/types-catalogs/metadata',
}
version: { type: 'string' },
configSchema: { type: 'object' },
listFiltersSchema: { type: 'object' },
importFiltersSchema: { type: 'object' },
publicationFiltersSchema: { type: 'object' },
importConfigSchema: { type: 'object' },
metadata: { $ref: 'https://github.com/data-fair/types-catalogs/metadata' }
}
}
143 changes: 32 additions & 111 deletions api/types/publication/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,25 @@ export default {
owner: {
$ref: 'https://github.com/data-fair/lib/account'
},
catalog: {
type: 'object',
additionalProperties: false,
required: ['id'],
title: 'Catalog',
'x-i18n-title': {
fr: 'Catalogue'
},
properties: {
id: {
type: 'string',
description: 'Id of the catalog'
},
title: {
type: 'string',
description: 'Title of the catalog'
}
}
},
created: {
type: 'object',
additionalProperties: false,
Expand Down Expand Up @@ -70,38 +89,14 @@ export default {
fr: 'Action à effectuer dans le catalogue distant'
},
enum: [
'create',
'addAsResource',
'overwrite',
'createFolderInRoot',
'createFolder',
'createResource',
'replaceFolder',
'replaceResource',
'delete'
],
layout: {
cols: 6,
items: [
{
title: 'Create a new dataset',
'x-i18n-title': {
fr: 'Créer un nouveau jeu de données'
},
value: 'create'
},
{
title: 'Add as resource to an existing dataset',
'x-i18n-title': {
fr: 'Ajouter comme ressource à un jeu de données existant'
},
value: 'addAsResource'
},
{
title: 'Overwrite an existing dataset',
'x-i18n-title': {
fr: 'Écraser un jeu de données existant'
},
value: 'overwrite'
}
// Do not show delete option in the form
]
}
layout: { cols: 6 }
},
publicationSite: {
type: 'object',
Expand Down Expand Up @@ -131,7 +126,10 @@ export default {
additionalProperties: false,
layout: {
getItems: {
url: '${context.origin}/data-fair/api/v1/datasets/${rootData.dataFairDataset?.id}',
url: {
expr: 'rootData.dataFairDataset?.id && `${context.origin}/data-fair/api/v1/datasets/${rootData.dataFairDataset.id}?select=publicationSites`',
type: 'js-eval'
},
itemsResults: 'data.publicationSites ?? []',
itemValue: 'context.publicationSites[item]',
itemTitle: '`${context.publicationSites[item]?.title} (${context.publicationSites[item]?.url})`',
Expand All @@ -144,43 +142,6 @@ export default {
}
}
},
catalog: {
type: 'object',
additionalProperties: false,
required: ['id'],
title: 'Catalog',
'x-i18n-title': {
fr: 'Catalogue'
},
properties: {
id: {
type: 'string',
description: 'Id of the catalog'
},
title: {
type: 'string',
description: 'Title of the catalog'
}
},
layout: {
cols: 6,
if: '!context.catalog.id',
props: {
placeholder: 'Search for a catalog',
'x-i18n-placeholder': {
fr: 'Rechercher...'
}
},
getItems: {
url: '${context.origin}/catalogs/api/catalogs?sort=updated.date:-1&select=_id,title&capabilities=publication',
itemsResults: 'data.results',
itemTitle: 'item.title',
itemValue: '{ id: item._id, title: item.title }',
itemKey: 'item.id',
qSearchParam: 'q',
}
}
},
dataFairDataset: {
type: 'object',
additionalProperties: false,
Expand All @@ -199,7 +160,6 @@ export default {
},
layout: {
cols: 6,
if: '!context.dataFairDataset.id',
props: {
placeholder: 'Search for a dataset',
'x-i18n-placeholder': {
Expand All @@ -215,14 +175,10 @@ export default {
}
}
},
remoteDataset: {
remoteFolder: {
type: 'object',
additionalProperties: false,
required: ['id'],
title: 'Remote dataset',
'x-i18n-title': {
fr: 'Jeu de données distant'
},
properties: {
id: {
type: 'string'
Expand All @@ -232,24 +188,7 @@ export default {
},
url: {
type: 'string',
description: 'URL to view the dataset in the remote catalog'
}
},
layout: {
if: "parent.data.action === 'addAsResource' || parent.data.action === 'overwrite'",
props: {
placeholder: 'Search in the remote catalog...',
'x-i18n-placeholder': {
fr: 'Rechercher dans le catalogue distant...'
}
},
getItems: {
url: '${context.origin}/catalogs/api/catalogs/${rootData.catalog.id}/datasets?mode=${rootData.action}',
itemsResults: 'data.results',
itemTitle: '`${item.title} (${item.id})`',
itemValue: '{ id: item.id, title: item.title }',
itemKey: 'item.id',
qSearchParam: 'q'
description: 'URL to view the folder in the remote catalog (if available)'
}
}
},
Expand All @@ -266,7 +205,7 @@ export default {
},
url: {
type: 'string',
description: 'URL to view the resource in the remote catalog'
description: 'URL to view the resource in the remote catalog (if available)'
}
}
},
Expand All @@ -291,29 +230,11 @@ export default {
readOnly: true
}
},
// JSON-Schema-to-typescript does not support conditional required properties,
// but VJSF support it partially with `oneOf`, so we add it dynamically in the UI.
// https://stackoverflow.com/questions/38717933/jsonschema-attribute-conditionally-required
// oneOf: [
// {
// properties: {
// action: { enum: ['addAsResource', 'overwrite'] }
// },
// required: ['remoteDataset']
// },
// {
// properties: {
// action: { enum: ['create', 'delete'] }
// }
// }
// ],
layout: {
title: null,
children: [
'catalog',
'dataFairDataset',
'action',
'remoteDataset',
'publicationSite'
]
}
Expand Down
Loading