3IXYDVZ3CQBTNPSCYE3ZO7FVBAYF25HX5BAYEUERHYU3GRYZ7TRAC LOKVDNBE475AI42AEO2LKK6EQKA5GGSZTS6VWJEQBV23E4TWTFCAC VJKKUQQBL56NHDKV4C3VVKAYHZDDWCPQ5F2WEMF6UYICLPHMVQTAC DZURLJKGFFIAG4KUZTLCURERBXHFWNSNUFYYB7PXCXFUJWPIHWZQC EQKLVT45KJNHQCSI56WCHTB4UKMLOIOXBXXEJ6JH46GME2RKTSWQC OBXY6BHNROIV7W4O3MMQ6GTQXU2XUKF5A4DCJYDLUEUHV72E7BNAC TKCG7EZ3MYG2FFWYRLGQTQXIRDSNGICDHK6CE6OZJPTBK6CNYFJAC 4667MSQANSFBWQVT7EDH7ZGWYVGNPCVLQRIXOECHY2BMS7V4SYUQC UFYD7GLFT2JHHKDWCHDUUYS42SGIVLFLRHQLZC6UYHSDOCHYAJAAC 6W73J6WJQHALRF4ZLBJ4JH6H7UYT74KFPKFH537T2BRW2G6MCLRQC DBDM3MJDJ5TRAH3FA7MLBGFUPZF4GIM4CPZAKFNS5MV3UYQOAFNQC SQE6BJ4BAANJ4N5HT6QC6DIQQEMNXGR46HVAFJUAX4WIVW352KKQC H2IBYJKO64HU6N2V2WUQPONZZGKAHLR7EG7T2UYMY5ENHSGGZZJAC KNQ2D44EXRDVEF452BX32KPW4SBRLCX2KMNL6UHDYBED7JBXTU6QC QPZE42LFURFXW6QNTVXY6AF7DZI7KTOLHMNSJHLFCRZMNMGQ6EVQC M3JUJ2WWZGCVMBITKRM5FUJMHFYL2QRMXJUVRUE4AC2RF74AOL5AC YX7LU4WRAUDMWS3DEDXZDSF6DXBHLYDWVSMSRK6KIW3MO6GRXSVQC 7NCQHN4SFSVX5YACHUZLKCWMA2WNMSU4WCZBPY3CH2KSELBX4RTQC T4EU44HLZGB4GRJWFZYR72H2FACCMDPD2R3PCBAUFKCCIOAQRJWAC LSAQ6ZM2NELU3FIWKEFBOXKVLSZS2ZOK2PHPHJRWPVZ5CVILSUYQC // Create admin access grants for the dataset creator// Grant all permissions (READ, UPLOAD, DOWNLOAD, EDIT, DELETE) to the creatorconst permissions = ['READ', 'UPLOAD', 'DOWNLOAD', 'EDIT', 'DELETE'] as const;const accessGrants = permissions.map(permission => ({id: nanoid(12),datasetId: result[0].id,role: 'ADMIN' as const,permission,userId: userId,createdBy: userId,createdAt: now,lastModified: now,modifiedBy: userId,active: true,}));await db.insert(accessGrant).values(accessGrants);
*//*** Protected API route to get cluster location coordinates** @route GET /api/clusters/:id/location* @authentication Required* @param {string} id - Cluster ID in URL path* @returns {Object} Response containing:* - data: { latitude: number, longitude: number }* @description Gets the location coordinates for a cluster. Requires READ permission on the dataset.
clusters.get("/:id/location", authenticate, async (c) => {try {const jwtPayload = (c as unknown as { jwtPayload: JWTPayload }).jwtPayload;const userId = jwtPayload.sub;const clusterId = c.req.param("id");const db = createDatabase(c.env);// Get cluster and its location coordinatesconst clusterWithLocation = await db.select({datasetId: cluster.datasetId,latitude: location.latitude,longitude: location.longitude}).from(cluster).leftJoin(location, eq(cluster.locationId, location.id)).where(eq(cluster.id, clusterId)).limit(1);if (clusterWithLocation.length === 0) {return c.json({error: "Cluster not found"}, 404);}const clusterData = clusterWithLocation[0];// Check if user has READ permission on the datasetconst hasPermission = await checkUserPermission(db, userId, clusterData.datasetId, 'READ');if (!hasPermission) {return c.json({error: "You don't have permission to access this cluster"}, 403);}// Validate that location coordinates are availableif (clusterData.latitude === null || clusterData.longitude === null) {return c.json({error: "Location coordinates not available for this cluster"}, 404);}return c.json({data: {latitude: parseFloat(clusterData.latitude),longitude: parseFloat(clusterData.longitude)}});} catch (error) {return c.json(standardErrorResponse(error, "fetching cluster location"), 500);}});
};// Determine timestamp for astronomical calculationslet timestampUTC: string;let timestampLocal: string;if (metadata.isAudioMoth && metadata.audioMothData) {timestampUTC = metadata.audioMothData.timestampUTC;timestampLocal = metadata.audioMothData.timestampLocal;} else if (precomputedTimestamp) {timestampUTC = precomputedTimestamp.timestampUTC;timestampLocal = precomputedTimestamp.timestampLocal;} else {// Fallback to current time if no timestamp availableconsole.warn(`No timestamp data available for file: ${file.name}`);const now = new Date();timestampUTC = now.toISOString();timestampLocal = now.toISOString();}// Calculate astronomical data if cluster location is availablelet astronomicalData = null;if (clusterLocation && basicInfo.duration) {try {astronomicalData = calculateAstronomicalData(timestampUTC,basicInfo.duration,clusterLocation);} catch (error) {console.error('Error calculating astronomical data:', error);}}const completeFileData = {...baseFileData,timestampLocal,maybeSolarNight: astronomicalData?.maybeSolarNight || null,maybeCivilNight: astronomicalData?.maybeCivilNight || null,moonPhase: astronomicalData?.moonPhase || null,
// For non-AudioMoth files, use precomputed timestamp from filename parsingif (precomputedTimestamp) {return {...baseFileData,timestampLocal: precomputedTimestamp.timestampLocal,// No moth metadata for non-AudioMoth files};} else {// Fallback to current time if no precomputed timestamp availableconsole.warn(`No timestamp data available for non-AudioMoth file: ${file.name}`);return {...baseFileData,timestampLocal: new Date().toISOString(),// No moth metadata for non-AudioMoth files};}
return completeFileData;
// Fetch cluster location coordinatesconst locationResponse = await fetch(`/api/clusters/${encodeURIComponent(clusterId)}/location`, {headers: {Authorization: `Bearer ${accessToken}`,},});if (locationResponse.ok) {const locationResult = await locationResponse.json();setClusterLocation(locationResult.data);console.log(`Cluster location: lat=${locationResult.data.latitude}, lng=${locationResult.data.longitude}`);} else {console.error('Failed to fetch cluster location coordinates');setClusterLocation(null);}
}, [selectedFolder, folderPath, scanAudioFiles, onImportComplete, handleClose, clusterId, datasetId, locationId, clusterTimezone, isAuthenticated, getAccessToken]);
}, [selectedFolder, folderPath, scanAudioFiles, onImportComplete, handleClose, clusterId, datasetId, locationId, clusterTimezone, clusterLocation, isAuthenticated, getAccessToken]);