@@ -4,19 +4,120 @@ import path from 'path';
44import FormData from 'form-data' ;
55import nodeFetch from 'node-fetch' ;
66
7- import * as readme from '../../assets/README.template.js' ;
87import { createModuleLogger } from '../../logger/index.js' ;
98
109const logger = createModuleLogger ( 'datagouv' ) ;
1110
1211const DATASET_LICENSE = 'odc-odbl' ;
1312const DEFAULT_RESOURCE_DESCRIPTION = 'See README.md inside the archive for dataset structure and usage information.' ;
1413
15- export async function updateDatasetMetadata ( { apiBaseUrl, headers, datasetId, releaseDate, stats } ) {
14+ const routes = {
15+ dataset : ( apiBaseUrl , datasetId ) => `${ apiBaseUrl } /datasets/${ datasetId } /` ,
16+ datasets : apiBaseUrl => `${ apiBaseUrl } /datasets/` ,
17+ datasetUpload : ( apiBaseUrl , datasetId ) => `${ apiBaseUrl } /datasets/${ datasetId } /upload/` ,
18+ resource : ( apiBaseUrl , datasetId , resourceId ) => `${ apiBaseUrl } /datasets/${ datasetId } /resources/${ resourceId } /` ,
19+ resourceUpload : ( apiBaseUrl , datasetId , resourceId ) => `${ apiBaseUrl } /datasets/${ datasetId } /resources/${ resourceId } /upload/` ,
20+ organization : ( apiBaseUrl , organizationIdOrSlug ) => `${ apiBaseUrl } /organizations/${ organizationIdOrSlug } /` ,
21+ organizationDatasets : ( apiBaseUrl , organizationId ) => `${ apiBaseUrl } /organizations/${ organizationId } /datasets/?page_size=100` ,
22+ } ;
23+
24+ export async function getOrganization ( { apiBaseUrl, headers, organizationIdOrSlug } ) {
25+ logger . info ( `Fetching organization: ${ organizationIdOrSlug } …` ) ;
26+
27+ const orgResponse = await nodeFetch ( routes . organization ( apiBaseUrl , organizationIdOrSlug ) , { headers } ) ;
28+
29+ if ( ! orgResponse . ok ) {
30+ const errorText = await orgResponse . text ( ) ;
31+
32+ throw new Error ( `Failed to retrieve organization: ${ orgResponse . status } ${ orgResponse . statusText } - ${ errorText } ` ) ;
33+ }
34+
35+ const orgData = await orgResponse . json ( ) ;
36+
37+ logger . info ( `Found organization: ${ orgData . name } (ID: ${ orgData . id } )` ) ;
38+
39+ return orgData ;
40+ }
41+
42+ export async function getDataset ( { apiBaseUrl, headers, datasetId } ) {
43+ const datasetResponse = await nodeFetch ( routes . dataset ( apiBaseUrl , datasetId ) , { headers } ) ;
44+
45+ if ( ! datasetResponse . ok ) {
46+ const errorText = await datasetResponse . text ( ) ;
47+ const error = new Error ( `Failed to retrieve dataset: ${ datasetResponse . status } ${ datasetResponse . statusText } - ${ errorText } ` ) ;
48+
49+ error . statusCode = datasetResponse . status ;
50+ throw error ;
51+ }
52+
53+ const datasetData = await datasetResponse . json ( ) ;
54+
55+ return datasetData ;
56+ }
57+
58+ export async function findDatasetByTitle ( { apiBaseUrl, headers, organizationId, title } ) {
59+ logger . info ( `Searching for dataset with title "${ title } " in organization…` ) ;
60+
61+ const searchResponse = await nodeFetch ( routes . organizationDatasets ( apiBaseUrl , organizationId ) , { headers } ) ;
62+
63+ if ( ! searchResponse . ok ) {
64+ const errorText = await searchResponse . text ( ) ;
65+
66+ throw new Error ( `Failed to search for datasets: ${ searchResponse . status } ${ searchResponse . statusText } - ${ errorText } ` ) ;
67+ }
68+
69+ const searchData = await searchResponse . json ( ) ;
70+
71+ const dataset = searchData . data . find ( ds => ds . title === title ) ;
72+
73+ if ( dataset ) {
74+ logger . info ( `Found existing dataset: ${ dataset . title } (ID: ${ dataset . id } )` ) ;
75+
76+ return dataset ;
77+ }
78+
79+ logger . info ( 'No existing dataset found with this title' ) ;
80+
81+ return null ;
82+ }
83+
84+ export async function createDataset ( { apiBaseUrl, headers, organizationId, title, description, license, frequency } ) {
85+ logger . info ( `Creating new dataset: ${ title } …` ) ;
86+
87+ const createResponse = await nodeFetch ( routes . datasets ( apiBaseUrl ) , {
88+ method : 'POST' ,
89+ headers : {
90+ ...headers ,
91+ 'Content-Type' : 'application/json' ,
92+ } ,
93+ body : JSON . stringify ( {
94+ title,
95+ description,
96+ organization : organizationId ,
97+ license,
98+ frequency,
99+ } ) ,
100+ } ) ;
101+
102+ if ( ! createResponse . ok ) {
103+ const errorText = await createResponse . text ( ) ;
104+
105+ throw new Error ( `Failed to create dataset: ${ createResponse . status } ${ createResponse . statusText } - ${ errorText } ` ) ;
106+ }
107+
108+ const dataset = await createResponse . json ( ) ;
109+
110+ logger . info ( `Dataset created successfully: ${ dataset . title } (ID: ${ dataset . id } )` ) ;
111+
112+ return dataset ;
113+ }
114+
115+ export async function updateDatasetMetadata ( { apiBaseUrl, headers, datasetId, title, description, stats, frequency } ) {
16116 const updatePayload = {
17- title : readme . title ( { releaseDate } ) ,
18- description : readme . body ( stats ) ,
117+ title,
118+ description,
19119 license : DATASET_LICENSE ,
120+ frequency,
20121 } ;
21122
22123 if ( stats ?. firstVersionDate && stats ?. lastVersionDate ) {
@@ -26,7 +127,7 @@ export async function updateDatasetMetadata({ apiBaseUrl, headers, datasetId, re
26127 } ;
27128 }
28129
29- const updateResponse = await nodeFetch ( ` ${ apiBaseUrl } /datasets/ ${ datasetId } /` , {
130+ const updateResponse = await nodeFetch ( routes . dataset ( apiBaseUrl , datasetId ) , {
30131 method : 'PUT' ,
31132 headers : {
32133 ...headers ,
@@ -37,25 +138,21 @@ export async function updateDatasetMetadata({ apiBaseUrl, headers, datasetId, re
37138
38139 if ( ! updateResponse . ok ) {
39140 const errorText = await updateResponse . text ( ) ;
141+ const error = new Error ( `Failed to update dataset metadata: ${ updateResponse . status } ${ updateResponse . statusText } - ${ errorText } ` ) ;
40142
41- throw new Error ( `Failed to update dataset metadata: ${ updateResponse . status } ${ updateResponse . statusText } - ${ errorText } ` ) ;
143+ error . statusCode = updateResponse . status ;
144+ throw error ;
42145 }
146+
147+ logger . info ( 'Dataset metadata updated successfully' ) ;
43148}
44149
45150export async function uploadResource ( { apiBaseUrl, headers, datasetId, archivePath } ) {
46151 logger . info ( 'Uploading dataset archive…' ) ;
47152
48- const formData = new FormData ( ) ;
49- const fileName = path . basename ( archivePath ) ;
50- const fileStats = fsApi . statSync ( archivePath ) ;
153+ const { formData, fileName } = createFormDataForFile ( archivePath ) ;
51154
52- formData . append ( 'file' , fsApi . createReadStream ( archivePath ) , {
53- filename : fileName ,
54- contentType : 'application/zip' ,
55- knownLength : fileStats . size ,
56- } ) ;
57-
58- const uploadResponse = await nodeFetch ( `${ apiBaseUrl } /datasets/${ datasetId } /upload/` , {
155+ const uploadResponse = await nodeFetch ( routes . datasetUpload ( apiBaseUrl , datasetId ) , {
59156 method : 'POST' ,
60157 headers : { ...formData . getHeaders ( ) , ...headers } ,
61158 body : formData ,
@@ -74,10 +171,34 @@ export async function uploadResource({ apiBaseUrl, headers, datasetId, archivePa
74171 return { resourceId : uploadResult . id , fileName } ;
75172}
76173
174+ export async function replaceResourceFile ( { apiBaseUrl, headers, datasetId, resourceId, archivePath } ) {
175+ logger . info ( `Replacing file for existing resource ID: ${ resourceId } …` ) ;
176+
177+ const { formData, fileName } = createFormDataForFile ( archivePath ) ;
178+
179+ const uploadResponse = await nodeFetch ( routes . resourceUpload ( apiBaseUrl , datasetId , resourceId ) , {
180+ method : 'POST' ,
181+ headers : { ...formData . getHeaders ( ) , ...headers } ,
182+ body : formData ,
183+ } ) ;
184+
185+ if ( ! uploadResponse . ok ) {
186+ const errorText = await uploadResponse . text ( ) ;
187+
188+ throw new Error ( `Failed to replace resource file: ${ uploadResponse . status } ${ uploadResponse . statusText } - ${ errorText } ` ) ;
189+ }
190+
191+ const uploadResult = await uploadResponse . json ( ) ;
192+
193+ logger . info ( 'Resource file replaced successfully' ) ;
194+
195+ return { resourceId : uploadResult . id , fileName } ;
196+ }
197+
77198export async function updateResourceMetadata ( { apiBaseUrl, headers, datasetId, resourceId, fileName } ) {
78199 logger . info ( 'Updating resource metadata…' ) ;
79200
80- const resourceUpdateResponse = await nodeFetch ( ` ${ apiBaseUrl } /datasets/ ${ datasetId } /resources/ ${ resourceId } /` , {
201+ const resourceUpdateResponse = await nodeFetch ( routes . resource ( apiBaseUrl , datasetId , resourceId ) , {
81202 method : 'PUT' ,
82203 headers : { ...headers , 'Content-Type' : 'application/json' } ,
83204 body : JSON . stringify ( {
@@ -98,20 +219,16 @@ export async function updateResourceMetadata({ apiBaseUrl, headers, datasetId, r
98219 logger . info ( 'Resource metadata updated successfully' ) ;
99220}
100221
101- export async function getDatasetUrl ( { apiBaseUrl, headers, datasetId } ) {
102- const datasetResponse = await nodeFetch ( `${ apiBaseUrl } /datasets/${ datasetId } /` , {
103- method : 'GET' ,
104- headers : { ...headers } ,
105- } ) ;
106-
107- if ( ! datasetResponse . ok ) {
108- const errorText = await datasetResponse . text ( ) ;
109-
110- throw new Error ( `Failed to retrieve dataset URL: ${ datasetResponse . status } ${ datasetResponse . statusText } - ${ errorText } ` ) ;
111- }
222+ function createFormDataForFile ( archivePath ) {
223+ const formData = new FormData ( ) ;
224+ const fileName = path . basename ( archivePath ) ;
225+ const fileStats = fsApi . statSync ( archivePath ) ;
112226
113- const datasetData = await datasetResponse . json ( ) ;
114- const datasetUrl = datasetData . page ;
227+ formData . append ( 'file' , fsApi . createReadStream ( archivePath ) , {
228+ filename : fileName ,
229+ contentType : 'application/zip' ,
230+ knownLength : fileStats . size ,
231+ } ) ;
115232
116- return datasetUrl ;
233+ return { formData , fileName } ;
117234}
0 commit comments