observation-js
Version:
A fully-typed TypeScript client for the waarneming.nl API.
199 lines (198 loc) • 8.32 kB
JavaScript
export class Observations {
#client;
/**
* @internal
*/
constructor(client) {
this.#client = client;
}
/**
* Helper function to filter out undefined values from params
* @private
*/
filterParams(params) {
const filtered = {};
for (const [key, value] of Object.entries(params)) {
if (value !== undefined) {
filtered[key] = value;
}
}
return filtered;
}
/**
* Retrieves a single observation by its ID.
*
* @param id The unique identifier of the observation.
* @returns A promise that resolves to the observation object.
* @throws {AuthenticationError} If the request is not authenticated.
* @throws {ApiError} If the request fails.
*/
async get(id) {
return this.#client.request(`observations/${id}`);
}
/**
* Creates a new observation.
*
* @param payload - The core data for the new observation.
* @param options - Optional parameters, including media files to upload synchronously.
* @returns A promise that resolves to the newly created observation object.
* @throws {AuthenticationError} If the request is not authenticated.
* @throws {ApiError} If the request fails.
*/
async create(payload, options = {}) {
const { upload_photos, upload_sounds } = options;
if (upload_photos?.length || upload_sounds?.length) {
const formData = new FormData();
formData.append('observation', JSON.stringify(payload));
upload_photos?.forEach((photo) => formData.append('upload_photos', photo));
upload_sounds?.forEach((sound) => formData.append('upload_sounds', sound));
return this.#client.request('observations/create-single/', {
method: 'POST',
body: formData,
});
}
return this.#client.request('observations/create-single/', {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json',
},
});
}
/**
* Updates an existing observation.
*
* @param id - The unique identifier of the observation to update.
* @param payload - The data to update on the observation.
* @param options - Optional parameters, including media files to upload synchronously.
* @returns A promise that resolves to the updated observation object.
* @throws {AuthenticationError} If the request is not authenticated.
* @throws {ApiError} If the request fails.
*/
async update(id, payload, options = {}) {
const { upload_photos, upload_sounds } = options;
if (upload_photos?.length || upload_sounds?.length) {
const formData = new FormData();
formData.append('observation', JSON.stringify(payload));
upload_photos?.forEach((photo) => formData.append('upload_photos', photo));
upload_sounds?.forEach((sound) => formData.append('upload_sounds', sound));
return this.#client.request(`observations/${id}/update/`, {
method: 'POST',
body: formData,
});
}
return this.#client.request(`observations/${id}/update/`, {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json',
},
});
}
/**
* Deletes an observation by its ID.
*
* @param id The unique identifier of the observation to delete.
* @returns A promise that resolves when the observation is successfully deleted.
* @throws {AuthenticationError} If the request is not authenticated.
* @throws {ApiError} If the request fails.
*/
async delete(id) {
await this.#client.request(`observations/${id}/delete/`, {
method: 'POST',
});
}
/**
* Search/list observations with filtering options.
* Note: General observation listing may not be available. Use more specific methods like getBySpecies, getByLocation, etc.
*
* @param params - Search and filtering parameters.
* @returns A promise that resolves to a paginated list of observations.
* @throws {ApiError} If the request fails.
*/
async search(params = {}) {
// Try alternative endpoints since general observations/ might not exist
if (params.species) {
return this.getBySpecies(params.species, params);
}
if (params.user) {
return this.getByUser(params.user, params);
}
// Fallback to around-point search if lat/lng provided in extended params
throw new Error('General observation search not available. Use getBySpecies(), getByUser(), getByLocation(), or getAroundPoint() instead.');
}
/**
* Retrieves observations for a specific species.
*
* @param speciesId - The unique identifier of the species.
* @param params - Optional filtering parameters.
* @returns A promise that resolves to a paginated list of observations.
* @throws {ApiError} If the request fails.
*/
async getBySpecies(speciesId, params = {}) {
return this.#client.publicRequest(`species/${speciesId}/observations/`, { params: this.filterParams(params) });
}
/**
* Retrieves related species observations for a specific species.
* Requires authentication.
*
* @param speciesId - The unique identifier of the species.
* @param params - Optional filtering parameters.
* @returns A promise that resolves to a paginated list of observations.
* @throws {AuthenticationError} If the request is not authenticated.
* @throws {ApiError} If the request fails.
*/
async getRelatedBySpecies(speciesId, params = {}) {
return this.#client.request(`species/${speciesId}/related-observations/`, { params: this.filterParams(params) });
}
/**
* Retrieves observations for a specific user.
* If no userId is provided, returns observations for the authenticated user.
* Requires authentication.
*
* @param userId - The unique identifier of the user (optional for current user).
* @param params - Optional filtering parameters.
* @returns A promise that resolves to a paginated list of observations.
* @throws {AuthenticationError} If the request is not authenticated.
* @throws {ApiError} If the request fails.
*/
async getByUser(userId, params = {}) {
const endpoint = userId ? `user/${userId}/observations/` : 'user/observations/';
return this.#client.request(endpoint, { params: this.filterParams(params) });
}
/**
* Retrieves observations for a specific location.
*
* @param locationId - The unique identifier of the location.
* @param params - Optional filtering parameters.
* @returns A promise that resolves to a paginated list of observations.
* @throws {ApiError} If the request fails.
*/
async getByLocation(locationId, params = {}) {
return this.#client.publicRequest(`locations/${locationId}/observations/`, { params: this.filterParams(params) });
}
/**
* Retrieves observations around a specific geographic point.
*
* @param params - Point coordinates and search parameters.
* @returns A promise that resolves to a paginated list of observations.
* @throws {ApiError} If the request fails.
*/
async getAroundPoint(params) {
return this.#client.publicRequest('observations/around-point/', { params: this.filterParams(params) });
}
/**
* Retrieves observations that were deleted after a specific timestamp.
* Requires authentication.
*
* @param modifiedAfter - ISO timestamp to get deletions after this point.
* @returns A promise that resolves to a list of deleted observation IDs.
* @throws {AuthenticationError} If the request is not authenticated.
* @throws {ApiError} If the request fails.
*/
async getDeleted(modifiedAfter) {
return this.#client.request('observations/deleted/', {
params: { modified_after: modifiedAfter },
});
}
}