• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GrottoCenter / grottocenter-api / 16492376263

21 Jul 2025 03:22PM UTC coverage: 41.791% (-0.8%) from 42.622%
16492376263

Pull #1386

github

camillernd
feat(swagger): add OAI-PMH bibliographic metadata endpoints
Pull Request #1386: feat(oai): add endpoints for OAI-PMH support (GetRecord, ListRecords,…

729 of 2484 branches covered (29.35%)

Branch coverage included in aggregate %.

15 of 126 new or added lines in 6 files covered. (11.9%)

2 existing lines in 1 file now uncovered.

2501 of 5245 relevant lines covered (47.68%)

4.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

1.52
/api/services/BibliographicMetadataService.js
1
/**
2
 * Bibliographic Metadata Service
3
 *
4
 * Service layer for managing bibliographic metadata records in compliance with Z39.50 and OAI-PMH (Open Archives Initiative
5
 * Protocol for Metadata Harvesting) specifications. This service provides comprehensive data access methods
6
 * for bibliographic metadata operations including record retrieval, filtering, counting, and set management.
7
 *
8
 * Key Features:
9
 * - OAI-PMH compliant record retrieval and filtering
10
 * - Date range filtering with ISO 8601 support
11
 * - Set-based record organization and filtering
12
 * - Metadata status management (registered/deleted)
13
 *
14
 * Database Model: TBibliographicMetadata
15
 */
16

17
// Enumeration of valid metadata status values for bibliographic records
18
const METADATA_STATUS = {
1✔
19
  REGISTERED: 'registered', // Active records available for harvesting
20
  DELETED: 'deleted', // Soft-deleted records (may still be visible based on includeDeleted parameter)
21
};
22

23
module.exports = {
1✔
24
  /**
25
   * Retrieves a Single Bibliographic Metadata Record by ID
26
   *
27
   * Fetches a specific bibliographic metadata record using its unique identifier.
28
   * This method supports filtering by metadata status to control visibility of
29
   * deleted or inactive records.
30
   *
31
   * @param {string} id - Unique identifier of the bibliographic metadata record
32
   * @param {boolean} [registeredOnly=true] - Filter to include only active registered records
33
   * @returns {Promise<Object|null>} Bibliographic metadata record or null if not found
34
   * @throws {Error} Database query errors
35
   */
36
  async getMetadata(id, registeredOnly = true) {
×
37
    // Build query criteria starting with the required ID
UNCOV
38
    const criteria = { id };
×
39

40
    // Apply status filter if only registered records are requested
41
    if (registeredOnly) {
×
42
      criteria.metadataStatus = METADATA_STATUS.REGISTERED;
×
43
    }
44

45
    // Execute database query to find the specific record
UNCOV
46
    const Record = await sails.models.tbibliographicmetadata.findOne(criteria);
×
47

48
    // Return null if no matching record is found
49
    if (!Record) {
×
50
      return null;
×
51
    }
52

53
    return Record;
×
54
  },
55

56
  /**
57
   * Retrieves All Distinct OAI-PMH Set Specifications
58
   *
59
   * Extracts and returns a sorted list of all unique OAI-PMH set specifications
60
   * from the bibliographic metadata repository. This method implements the ListSets
61
   * verb functionality by aggregating set data from all records.
62
   *
63
   * @param {boolean} [registeredOnly=true] - Filter to include only sets from active registered records
64
   * @returns {Promise<string[]>} Sorted array of distinct set specifications
65
   * @throws {Error} Database query or processing errors
66
   */
67
  async getDistinctSets(registeredOnly = true) {
×
NEW
68
    try {
×
69
      // Initialize query criteria object
NEW
70
      const criteria = {};
×
71

72
      // Apply metadata status filter if only registered records are requested
NEW
73
      if (registeredOnly) {
×
NEW
74
        criteria.metadataStatus = METADATA_STATUS.REGISTERED;
×
75
      }
76

77
      // Fetch records with only the listSets field to optimize query performance
NEW
78
      const records = await sails.models.tbibliographicmetadata.find({
×
79
        where: criteria,
80
        select: ['listSets'],
81
      });
82

83
      // Use Set data structure to automatically handle uniqueness
NEW
84
      const allSets = new Set();
×
85

86
      // Process each record to extract and normalize set specifications
NEW
87
      records.forEach((record) => {
×
88
        // Ensure listSets exists and is an array
NEW
89
        if (record.listSets && Array.isArray(record.listSets)) {
×
NEW
90
          record.listSets.forEach((set) => {
×
91
            // Clean and validate each set specification
NEW
92
            if (set && set.trim()) {
×
NEW
93
              allSets.add(set.trim());
×
94
            }
95
          });
96
        }
97
      });
98

99
      // Convert Set to sorted array for consistent output
NEW
100
      return Array.from(allSets).sort();
×
101
    } catch (error) {
NEW
102
      sails.log.error('Error in getDistinctSets:', error);
×
NEW
103
      throw error;
×
104
    }
105
  },
106

107
  /**
108
   * Retrieves Single Record by OAI-PMH Identifier
109
   *
110
   * Implements the GetRecord verb of the OAI-PMH protocol by fetching a specific
111
   * bibliographic metadata record using its unique OAI identifier. Supports
112
   * flexible filtering based on metadata status and other criteria.
113
   *
114
   * @param {string} identifier - Unique OAI-PMH identifier for the target record
115
   * @param {Object} [filter={metadataStatus: 'registered'}] - Query filter criteria
116
   * @param {string} [filter.metadataStatus] - Metadata status filter ('registered' or 'deleted')
117
   * @returns {Promise<Object|null>} Complete bibliographic metadata record or null if not found
118
   * @throws {Error} Database query errors or invalid identifier format
119
   */
120
  async getOAIRecord(
121
    identifier,
122
    filter = { metadataStatus: METADATA_STATUS.REGISTERED }
×
123
  ) {
NEW
124
    try {
×
125
      // Construct Waterline criteria combining identifier and status filter
NEW
126
      const criteria = {
×
127
        oaiIdentifier: identifier,
128
        ...filter,
129
      };
130

131
      // Fetch single record matching criteria
NEW
132
      return await sails.models.tbibliographicmetadata.findOne(criteria);
×
133
    } catch (error) {
NEW
134
      sails.log.error('Error in getRecord:', error);
×
NEW
135
      throw error;
×
136
    }
137
  },
138

139
  /**
140
   * Retrieves Complete Bibliographic Records with OAI-PMH Filtering
141
   *
142
   * Implements the ListRecords verb of the OAI-PMH protocol by returning complete
143
   * bibliographic metadata records that match the specified criteria. Supports
144
   * date range filtering, set-based filtering, and metadata status filtering.
145
   *
146
   * @param {Object} [parameters={}] - OAI-PMH query parameters for filtering
147
   * @param {string} [parameters.set] - OAI-PMH set specification to filter records
148
   * @param {string} [parameters.from] - Start date for date range filtering (ISO 8601 format)
149
   * @param {string} [parameters.until] - End date for date range filtering (ISO 8601 format)
150
   * @param {string} [filter.metadataStatus] - Metadata status filter ('registered' or 'deleted')
151
   * @returns {Promise<Array<Object>>} Array of complete bibliographic metadata records
152
   * @throws {Error} Database query errors or invalid date format
153
   */
154
  async getOAIRecords(
155
    parameters = {},
×
156
    filter = { metadataStatus: METADATA_STATUS.REGISTERED }
×
157
  ) {
NEW
158
    try {
×
159
      // Start with the provided filter criteria (typically metadata status)
NEW
160
      const criteria = { ...filter };
×
161

162
      // Build date range query if from/until parameters are provided
NEW
163
      if (parameters.from || parameters.until) {
×
NEW
164
        criteria.lastUpdate = {};
×
165

166
        // Add greater-than-or-equal constraint for 'from' date
NEW
167
        if (parameters.from) {
×
NEW
168
          criteria.lastUpdate['>='] = new Date(parameters.from);
×
169
        }
170

171
        // Add less-than-or-equal constraint for 'until' date
NEW
172
        if (parameters.until) {
×
NEW
173
          criteria.lastUpdate['<='] = new Date(parameters.until);
×
174
        }
175
      }
176

177
      // Execute database query with non-array filters (Waterline doesn't support array filtering)
NEW
178
      let records = await sails.models.tbibliographicmetadata.find(criteria);
×
179

180
      // Apply set filtering in JavaScript since listSets is an array field
181
      // This is necessary because SQL/Waterline array operations are limited
NEW
182
      if (parameters.set) {
×
NEW
183
        records = records.filter(
×
184
          (record) =>
NEW
185
            Array.isArray(record.listSets) &&
×
186
            record.listSets.includes(parameters.set)
187
        );
188
      }
189

NEW
190
      return records;
×
191
    } catch (error) {
NEW
192
      sails.log.error('Error in recordsQuery:', error);
×
NEW
193
      throw error;
×
194
    }
195
  },
196

197
  /**
198
   * Retrieves OAI-PMH Identifiers with Minimal Metadata
199
   *
200
   * Implements the ListIdentifiers verb of the OAI-PMH protocol by returning only
201
   * essential header information (identifiers, timestamps, sets) without full metadata
202
   * content. This provides an efficient way to discover available records for harvesting.
203
   *
204
   * @param {Object} [parameters={}] - OAI-PMH query parameters for filtering
205
   * @param {string} [parameters.set] - OAI-PMH set specification to filter records
206
   * @param {string} [parameters.from] - Start date for date range filtering (ISO 8601 format)
207
   * @param {string} [parameters.until] - End date for date range filtering (ISO 8601 format)
208
   * @param {Object} [filter={metadataStatus: 'registered'}] - Additional filter criteria
209
   * @param {string} [filter.metadataStatus] - Metadata status filter ('registered' or 'deleted')
210
   * @returns {Promise<Array<Object>>} Array of identifier objects with minimal metadata
211
   * @returns {string} return[].oaiIdentifier - Unique OAI-PMH identifier
212
   * @returns {Date} return[].lastUpdate - Last modification timestamp
213
   * @returns {string[]} return[].listSets - Array of associated set specifications
214
   * @throws {Error} Database query errors or invalid date format
215
   */
216
  async getOAIIdentifiers(
217
    parameters = {},
×
218
    filter = { metadataStatus: METADATA_STATUS.REGISTERED }
×
219
  ) {
NEW
220
    try {
×
221
      // Start with the provided filter criteria (typically metadata status)
NEW
222
      const criteria = { ...filter };
×
223

224
      // Build date range query if from/until parameters are provided
NEW
225
      if (parameters.from || parameters.until) {
×
NEW
226
        criteria.lastUpdate = {};
×
227

228
        // Add greater-than-or-equal constraint for 'from' date
NEW
229
        if (parameters.from) {
×
NEW
230
          criteria.lastUpdate['>='] = new Date(parameters.from);
×
231
        }
232
        // Add less-than-or-equal constraint for 'until' date
NEW
233
        if (parameters.until) {
×
NEW
234
          criteria.lastUpdate['<='] = new Date(parameters.until);
×
235
        }
236
      }
237

238
      // Optimize query by selecting only essential fields for identifier response
239
      // This reduces network transfer and memory usage for large datasets
NEW
240
      let records = await sails.models.tbibliographicmetadata.find({
×
241
        where: criteria,
242
        select: ['oaiIdentifier', 'lastUpdate', 'listSets'],
243
      });
244

245
      // Apply set filtering in JavaScript since array operations are limited in SQL
246
      // This post-query filtering ensures accurate results for set-based queries
NEW
247
      if (parameters.set) {
×
NEW
248
        records = records.filter(
×
249
          (record) =>
NEW
250
            Array.isArray(record.listSets) &&
×
251
            record.listSets.includes(parameters.set)
252
        );
253
      }
254

255
      // Return records with listSets included for OAI-PMH header information
256
      // ListSets is required for proper OAI-PMH identifier responses
NEW
257
      return records;
×
258
    } catch (error) {
NEW
259
      sails.log.error('Error in identifiersQuery:', error);
×
NEW
260
      throw error;
×
261
    }
262
  },
263

264
  /**
265
   * Counts Bibliographic Records Matching OAI-PMH Criteria
266
   *
267
   * Provides an efficient count of bibliographic metadata records that match the
268
   * specified OAI-PMH parameters without retrieving the full record data. This is
269
   * useful for pagination and resource estimation in OAI-PMH implementations.
270
   *
271
   * @param {Object} [parameters={}] - OAI-PMH query parameters for filtering
272
   * @param {string} [parameters.set] - OAI-PMH set specification to filter records
273
   * @param {string} [parameters.from] - Start date for date range filtering (ISO 8601 format)
274
   * @param {string} [parameters.until] - End date for date range filtering (ISO 8601 format)
275
   * @param {Object} [filter={metadataStatus: 'registered'}] - Additional filter criteria
276
   * @param {string} [filter.metadataStatus] - Metadata status filter ('registered' or 'deleted')
277
   * @returns {Promise<number>} Total count of records matching the specified criteria
278
   * @throws {Error} Database query errors or invalid date format
279
   */
280
  async countRecords(
281
    parameters = {},
×
282
    filter = { metadataStatus: METADATA_STATUS.REGISTERED }
×
283
  ) {
NEW
284
    try {
×
285
      // Start with the provided filter criteria (typically metadata status)
NEW
286
      const criteria = { ...filter };
×
287

288
      // Build date range query if from/until parameters are provided
NEW
289
      if (parameters.from || parameters.until) {
×
NEW
290
        criteria.lastUpdate = {};
×
291

292
        // Add greater-than-or-equal constraint for 'from' date
NEW
293
        if (parameters.from) {
×
NEW
294
          criteria.lastUpdate['>='] = new Date(parameters.from);
×
295
        }
296
        // Add less-than-or-equal constraint for 'until' date
NEW
297
        if (parameters.until) {
×
NEW
298
          criteria.lastUpdate['<='] = new Date(parameters.until);
×
299
        }
300
      }
301

302
      // Optimize query by selecting only the listSets field needed for set filtering
303
      // This avoids transferring unnecessary data for counting operations
NEW
304
      let records = await sails.models.tbibliographicmetadata.find({
×
305
        where: criteria,
306
        select: ['listSets'],
307
      });
308

309
      // Apply set filtering in JavaScript if a specific set is requested
310
      // This is necessary because SQL array operations don't work well with Waterline
NEW
311
      if (parameters.set) {
×
NEW
312
        records = records.filter(
×
313
          (record) =>
NEW
314
            Array.isArray(record.listSets) &&
×
315
            record.listSets.includes(parameters.set)
316
        );
317
      }
318

319
      // Return the count of filtered records
NEW
320
      return records.length;
×
321
    } catch (error) {
NEW
322
      sails.log.error('Error in countPublication:', error);
×
NEW
323
      throw error;
×
324
    }
325
  },
326
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc