TKCG7EZ3MYG2FFWYRLGQTQXIRDSNGICDHK6CE6OZJPTBK6CNYFJAC
/**
* Protected API route to update an existing species
*
* @route PUT /api/species/:id
* @authentication Required
* @param {string} id - Species ID in URL path
* @param {Object} body - Species data to update (label, description, ebirdCode, active)
* @returns {Object} Response containing:
* - data: Updated species object with call types
* @description Updates an existing species. Requires EDIT permission on the dataset.
* When soft deleting (active=false), also soft deletes all related call types.
*/
return c.json({ error: "Not implemented yet" }, 501);
try {
const jwtPayload = (c as unknown as { jwtPayload: JWTPayload }).jwtPayload;
const userId = jwtPayload.sub;
const speciesId = c.req.param("id");
const body = await c.req.json();
const { label, description, ebirdCode, active } = body;
const db = createDatabase(c.env);
// Check if species exists and get its associated datasets
const existingSpecies = await db
.select({
id: species.id,
active: species.active
})
.from(species)
.where(eq(species.id, speciesId))
.limit(1);
if (existingSpecies.length === 0) {
return c.json({
error: "Species not found"
}, 404);
}
// Get datasets associated with this species to check permissions
const associatedDatasets = await db
.select({ datasetId: speciesDataset.datasetId })
.from(speciesDataset)
.where(eq(speciesDataset.speciesId, speciesId));
if (associatedDatasets.length === 0) {
return c.json({
error: "Species not associated with any dataset"
}, 400);
}
// Check if user has EDIT permission on at least one associated dataset
let hasPermission = false;
for (const assoc of associatedDatasets) {
if (await checkUserPermission(db, userId, assoc.datasetId, 'EDIT')) {
hasPermission = true;
break;
}
}
if (!hasPermission) {
return c.json({
error: "You don't have permission to edit this species"
}, 403);
}
// Start transaction for atomic update
const result = await db.transaction(async (tx) => {
// Build update object with only provided fields
const updateData: Record<string, unknown> = {
lastModified: new Date(),
modifiedBy: userId,
};
// Validate and add label if provided
if (label !== undefined) {
if (typeof label !== 'string' || label.trim().length === 0) {
return c.json({
error: "Field 'label' must be a non-empty string"
}, 400);
}
if (label.length > 100) {
return c.json({
error: "Field 'label' must be 100 characters or less"
}, 400);
}
updateData.label = label.trim();
}
// Validate and add description if provided
if (description !== undefined) {
if (description !== null && typeof description !== 'string') {
return c.json({
error: "Field 'description' must be a string or null"
}, 400);
}
if (description && description.length > 255) {
return c.json({
error: "Field 'description' must be 255 characters or less"
}, 400);
}
updateData.description = description?.trim() || null;
}
// Validate and add eBird code if provided
if (ebirdCode !== undefined) {
if (ebirdCode !== null && typeof ebirdCode !== 'string') {
return c.json({
error: "Field 'ebirdCode' must be a string or null"
}, 400);
}
if (ebirdCode && ebirdCode.length > 12) {
return c.json({
error: "Field 'ebirdCode' must be 12 characters or less"
}, 400);
}
updateData.ebirdCode = ebirdCode?.trim() || null;
updateData.taxonomyVersion = ebirdCode ? '2024' : null;
}
// Add active status if provided (for soft delete)
if (active !== undefined) {
if (typeof active !== 'boolean') {
return c.json({
error: "Field 'active' must be a boolean"
}, 400);
}
updateData.active = active;
// If soft deleting species, also soft delete related records
if (!active) {
// Soft delete all call types for this species
await tx
.update(callType)
.set({
active: false,
lastModified: new Date(),
modifiedBy: userId
})
.where(eq(callType.speciesId, speciesId));
// Delete all species-dataset associations for this species (hard delete since no active field)
await tx
.delete(speciesDataset)
.where(eq(speciesDataset.speciesId, speciesId));
}
}
// Update the species
const [updatedSpecies] = await tx
.update(species)
.set(updateData)
.where(eq(species.id, speciesId))
.returning({
id: species.id,
label: species.label,
ebirdCode: species.ebirdCode,
taxonomyVersion: species.taxonomyVersion,
description: species.description,
createdAt: species.createdAt,
createdBy: species.createdBy,
lastModified: species.lastModified,
modifiedBy: species.modifiedBy,
active: species.active,
});
// Get updated call types
const callTypes = await tx
.select({
id: callType.id,
label: callType.label,
})
.from(callType)
.where(and(eq(callType.speciesId, speciesId), eq(callType.active, true)));
return {
...updatedSpecies,
callTypes
};
});
console.log("Updated species:", speciesId, "by user:", userId);
return c.json({
data: result
});
} catch (error) {
console.error("Error updating species:", error);
return c.json(
{
error: "Failed to update species",
details: error instanceof Error ? error.message : String(error),
},
500
);
}
/**
* Protected API route to create a new cluster
*
* @route POST /api/clusters
* @authentication Required
* @param {Object} body - Cluster data including datasetId, locationId, name, description, timezoneId, sampleRate, recordingPattern
* - recordingPattern?: Object (optional) - { recordS: number, sleepS: number }
* @returns {Object} Response containing:
* - data: Created cluster object with optional recordingPattern
* @description Creates a new cluster with the authenticated user as owner.
* Requires EDIT permission on the dataset to create clusters within it.
* If recordingPattern is provided, creates a new cyclic recording pattern record.
*/
// Implementation would go here - similar to locations pattern
return c.json({ error: "Not implemented yet" }, 501);
try {
const jwtPayload = (c as unknown as { jwtPayload: JWTPayload }).jwtPayload;
const userId = jwtPayload.sub;
const db = createDatabase(c.env);
// Parse and validate request body
const body = await c.req.json();
const { id, datasetId, locationId, name, description, timezoneId, sampleRate, recordingPattern } = body;
// Field validation
if (!id || typeof id !== 'string') {
return c.json({
error: "Missing or invalid required field: id"
}, 400);
}
if (!datasetId || typeof datasetId !== 'string') {
return c.json({
error: "Missing or invalid required field: datasetId"
}, 400);
}
if (!locationId || typeof locationId !== 'string') {
return c.json({
error: "Missing or invalid required field: locationId"
}, 400);
}
if (!name || typeof name !== 'string' || name.trim().length === 0) {
return c.json({
error: "Missing or invalid required field: name"
}, 400);
}
if (!sampleRate || typeof sampleRate !== 'number' || sampleRate <= 0) {
return c.json({
error: "Missing or invalid required field: sampleRate"
}, 400);
}
// Length validation
if (id.length !== 12) {
return c.json({
error: "Field 'id' must be exactly 12 characters (nanoid)"
}, 400);
}
if (name.length > 140) {
return c.json({
error: "Field 'name' must be 140 characters or less"
}, 400);
}
if (description && description.length > 255) {
return c.json({
error: "Field 'description' must be 255 characters or less"
}, 400);
}
if (timezoneId && timezoneId.length > 40) {
return c.json({
error: "Field 'timezoneId' must be 40 characters or less"
}, 400);
}
// Validate recording pattern if provided
if (recordingPattern) {
if (typeof recordingPattern !== 'object' || recordingPattern === null) {
return c.json({
error: "Field 'recordingPattern' must be an object"
}, 400);
}
const { recordS, sleepS } = recordingPattern;
if (typeof recordS !== 'number' || recordS <= 0 || !Number.isInteger(recordS)) {
return c.json({
error: "Field 'recordingPattern.recordS' must be a positive integer"
}, 400);
}
if (typeof sleepS !== 'number' || sleepS <= 0 || !Number.isInteger(sleepS)) {
return c.json({
error: "Field 'recordingPattern.sleepS' must be a positive integer"
}, 400);
}
}
// Check if user has EDIT permission on the dataset
const hasPermission = await checkUserPermission(db, userId, datasetId.trim(), 'EDIT');
if (!hasPermission) {
return c.json({
error: "You don't have permission to create clusters in this dataset"
}, 403);
}
// Create cluster with optional recording pattern in transaction
const result = await db.transaction(async (tx) => {
const now = new Date();
let cyclicRecordingPatternId = null;
// Create cyclic recording pattern if provided
if (recordingPattern) {
const patternId = nanoid(12);
const [createdPattern] = await tx.insert(cyclicRecordingPattern).values({
id: patternId,
recordS: recordingPattern.recordS,
sleepS: recordingPattern.sleepS,
createdBy: userId,
createdAt: now,
lastModified: now,
modifiedBy: userId,
}).returning({
id: cyclicRecordingPattern.id,
});
cyclicRecordingPatternId = createdPattern.id;
}
// Create cluster object
const newCluster = {
id: id.trim(),
datasetId: datasetId.trim(),
locationId: locationId.trim(),
name: name.trim(),
description: description?.trim() || null,
timezoneId: timezoneId?.trim() || null,
cyclicRecordingPatternId: cyclicRecordingPatternId,
sampleRate: sampleRate,
createdBy: userId,
createdAt: now,
lastModified: now,
modifiedBy: userId,
active: true,
};
// Insert the cluster
const [createdCluster] = await tx.insert(cluster).values(newCluster).returning({
id: cluster.id,
datasetId: cluster.datasetId,
locationId: cluster.locationId,
name: cluster.name,
description: cluster.description,
timezoneId: cluster.timezoneId,
cyclicRecordingPatternId: cluster.cyclicRecordingPatternId,
sampleRate: cluster.sampleRate,
createdAt: cluster.createdAt,
createdBy: cluster.createdBy,
lastModified: cluster.lastModified,
modifiedBy: cluster.modifiedBy,
active: cluster.active,
});
// If recording pattern was created, include it in the response
const recordingPatternData = recordingPattern ? {
recordS: recordingPattern.recordS,
sleepS: recordingPattern.sleepS
} : null;
return {
...createdCluster,
recordingPattern: recordingPatternData
};
});
console.log("Created cluster:", result.id, "for location:", locationId, "by user:", userId);
return c.json({
data: result
}, 201);
} catch (error) {
console.error("Error creating cluster:", error);
// Handle unique constraint violations
if (error instanceof Error && error.message.includes('duplicate key')) {
return c.json({
error: "A cluster with this ID already exists"
}, 400);
}
return c.json(
{
error: "Failed to create cluster",
details: error instanceof Error ? error.message : String(error),
},
500
);
}
/**
* Protected API route to update an existing cluster
*
* @route PUT /api/clusters/:id
* @authentication Required
* @param {string} id - Cluster ID in URL path
* @param {Object} body - Cluster data to update (name, description, timezoneId, sampleRate, active, recordingPattern)
* - recordingPattern?: Object | null (optional) - { recordS: number, sleepS: number } or null to remove
* @returns {Object} Response containing:
* - data: Updated cluster object with recordingPattern if present
* @description Updates an existing cluster. Requires EDIT permission on the dataset.
* recordingPattern can be provided to add/update or set to null to remove recording pattern.
*/
// Implementation would go here - similar to locations pattern
return c.json({ error: "Not implemented yet" }, 501);
try {
const jwtPayload = (c as unknown as { jwtPayload: JWTPayload }).jwtPayload;
const userId = jwtPayload.sub;
const clusterId = c.req.param("id");
const body = await c.req.json();
const { name, description, timezoneId, sampleRate, active, recordingPattern } = body;
const db = createDatabase(c.env);
// Check if cluster exists and get its dataset ID and current recording pattern
const existingCluster = await db
.select({
id: cluster.id,
datasetId: cluster.datasetId,
active: cluster.active,
cyclicRecordingPatternId: cluster.cyclicRecordingPatternId
})
.from(cluster)
.where(eq(cluster.id, clusterId))
.limit(1);
if (existingCluster.length === 0) {
return c.json({
error: "Cluster not found"
}, 404);
}
const clusterRecord = existingCluster[0];
// Check if user has EDIT permission on the dataset
const hasEditPermission = await checkUserPermission(db, userId, clusterRecord.datasetId, 'EDIT');
if (!hasEditPermission) {
return c.json({
error: "You don't have permission to edit this cluster"
}, 403);
}
// Build update object with only provided fields
const updateData: Record<string, unknown> = {
lastModified: new Date(),
modifiedBy: userId,
};
// Validate and add name if provided
if (name !== undefined) {
if (typeof name !== 'string' || name.trim().length === 0) {
return c.json({
error: "Field 'name' must be a non-empty string"
}, 400);
}
if (name.length > 140) {
return c.json({
error: "Field 'name' must be 140 characters or less"
}, 400);
}
updateData.name = name.trim();
}
// Validate and add description if provided
if (description !== undefined) {
if (description !== null && typeof description !== 'string') {
return c.json({
error: "Field 'description' must be a string or null"
}, 400);
}
if (description && description.length > 255) {
return c.json({
error: "Field 'description' must be 255 characters or less"
}, 400);
}
updateData.description = description?.trim() || null;
}
// Validate and add timezoneId if provided
if (timezoneId !== undefined) {
if (timezoneId !== null && typeof timezoneId !== 'string') {
return c.json({
error: "Field 'timezoneId' must be a string or null"
}, 400);
}
if (timezoneId && timezoneId.length > 40) {
return c.json({
error: "Field 'timezoneId' must be 40 characters or less"
}, 400);
}
updateData.timezoneId = timezoneId?.trim() || null;
}
// Validate and add sampleRate if provided
if (sampleRate !== undefined) {
if (typeof sampleRate !== 'number' || sampleRate <= 0) {
return c.json({
error: "Field 'sampleRate' must be a positive number"
}, 400);
}
updateData.sampleRate = sampleRate;
}
// Validate recording pattern if provided
if (recordingPattern !== undefined) {
if (recordingPattern !== null && typeof recordingPattern !== 'object') {
return c.json({
error: "Field 'recordingPattern' must be an object or null"
}, 400);
}
if (recordingPattern) {
const { recordS, sleepS } = recordingPattern;
if (typeof recordS !== 'number' || recordS <= 0 || !Number.isInteger(recordS)) {
return c.json({
error: "Field 'recordingPattern.recordS' must be a positive integer"
}, 400);
}
if (typeof sleepS !== 'number' || sleepS <= 0 || !Number.isInteger(sleepS)) {
return c.json({
error: "Field 'recordingPattern.sleepS' must be a positive integer"
}, 400);
}
}
}
// Add active status if provided (for soft delete)
if (active !== undefined) {
if (typeof active !== 'boolean') {
return c.json({
error: "Field 'active' must be a boolean"
}, 400);
}
updateData.active = active;
}
// Update cluster with optional recording pattern changes in transaction
const result = await db.transaction(async (tx) => {
const currentPatternId = clusterRecord.cyclicRecordingPatternId;
// Handle recording pattern updates
if (recordingPattern !== undefined) {
if (recordingPattern === null) {
// Remove recording pattern
if (currentPatternId) {
await tx.delete(cyclicRecordingPattern).where(eq(cyclicRecordingPattern.id, currentPatternId));
updateData.cyclicRecordingPatternId = null;
}
} else {
// Add or update recording pattern
if (currentPatternId) {
// Update existing pattern
await tx
.update(cyclicRecordingPattern)
.set({
recordS: recordingPattern.recordS,
sleepS: recordingPattern.sleepS,
lastModified: new Date(),
modifiedBy: userId,
})
.where(eq(cyclicRecordingPattern.id, currentPatternId));
} else {
// Create new pattern
const patternId = nanoid(12);
await tx.insert(cyclicRecordingPattern).values({
id: patternId,
recordS: recordingPattern.recordS,
sleepS: recordingPattern.sleepS,
createdBy: userId,
createdAt: new Date(),
lastModified: new Date(),
modifiedBy: userId,
});
updateData.cyclicRecordingPatternId = patternId;
}
}
}
// Update the cluster
const [updatedCluster] = await tx
.update(cluster)
.set(updateData)
.where(eq(cluster.id, clusterId))
.returning({
id: cluster.id,
datasetId: cluster.datasetId,
locationId: cluster.locationId,
name: cluster.name,
description: cluster.description,
timezoneId: cluster.timezoneId,
cyclicRecordingPatternId: cluster.cyclicRecordingPatternId,
sampleRate: cluster.sampleRate,
createdAt: cluster.createdAt,
createdBy: cluster.createdBy,
lastModified: cluster.lastModified,
modifiedBy: cluster.modifiedBy,
active: cluster.active,
});
// Include recording pattern data in response if it exists
let recordingPatternData = null;
if (updatedCluster.cyclicRecordingPatternId) {
if (recordingPattern !== undefined && recordingPattern !== null) {
// Use the provided pattern data
recordingPatternData = {
recordS: recordingPattern.recordS,
sleepS: recordingPattern.sleepS
};
} else {
// Fetch existing pattern data
const existingPattern = await tx
.select({
recordS: cyclicRecordingPattern.recordS,
sleepS: cyclicRecordingPattern.sleepS
})
.from(cyclicRecordingPattern)
.where(eq(cyclicRecordingPattern.id, updatedCluster.cyclicRecordingPatternId))
.limit(1);
if (existingPattern.length > 0) {
recordingPatternData = existingPattern[0];
}
}
}
return {
...updatedCluster,
recordingPattern: recordingPatternData
};
});
if (!result) {
return c.json({
error: "Failed to update cluster"
}, 500);
}
console.log("Updated cluster:", result.id, "by user:", userId);
return c.json({
data: result
});
} catch (error) {
console.error("Error updating cluster:", error);
return c.json(
{
error: "Failed to update cluster",
details: error instanceof Error ? error.message : String(error),
},
500
);
}
/**
* Protected API route to delete a cluster
*
* @route DELETE /api/clusters/:id
* @authentication Required
* @param {string} id - Cluster ID in URL path
* @returns {Object} Response containing:
* - message: Success message
* @description Soft deletes a cluster (sets active=false) and hard deletes its associated cyclic recording pattern if present.
* Requires EDIT permission on the dataset.
*/
// Implementation would go here - similar to locations pattern
return c.json({ error: "Not implemented yet" }, 501);
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);
// Check if cluster exists and get its dataset ID and recording pattern
const existingCluster = await db
.select({
id: cluster.id,
datasetId: cluster.datasetId,
cyclicRecordingPatternId: cluster.cyclicRecordingPatternId
})
.from(cluster)
.where(eq(cluster.id, clusterId))
.limit(1);
if (existingCluster.length === 0) {
return c.json({
error: "Cluster not found"
}, 404);
}
const clusterRecord = existingCluster[0];
// Check if user has EDIT permission on the dataset
const hasEditPermission = await checkUserPermission(db, userId, clusterRecord.datasetId, 'EDIT');
if (!hasEditPermission) {
return c.json({
error: "You don't have permission to delete this cluster"
}, 403);
}
// Soft delete cluster and hard delete associated recording pattern in transaction
await db.transaction(async (tx) => {
// Soft delete the cluster (set active = false)
await tx
.update(cluster)
.set({
active: false,
lastModified: new Date(),
modifiedBy: userId
})
.where(eq(cluster.id, clusterId));
// Hard delete associated cyclic recording pattern if it exists (no active field in this table)
if (clusterRecord.cyclicRecordingPatternId) {
await tx.delete(cyclicRecordingPattern).where(eq(cyclicRecordingPattern.id, clusterRecord.cyclicRecordingPatternId));
}
});
console.log("Soft deleted cluster:", clusterId, "and hard deleted associated recording pattern by user:", userId);
return c.json({
message: "Cluster deleted successfully"
});
} catch (error) {
console.error("Error deleting cluster:", error);
return c.json(
{
error: "Failed to delete cluster",
details: error instanceof Error ? error.message : String(error),
},
500
);
}
import { authenticate } from "../middleware/auth";
import type { Env } from "../types";
import { eq } from "drizzle-orm";
import {
callType,
speciesDataset
} from "../../../db/schema";
import { authenticate, checkUserPermission } from "../middleware/auth";
import { createDatabase } from "../utils/database";
import type { Env, JWTPayload } from "../types";
/**
* Protected API route to create a new call type
*
* @route POST /api/callTypes
* @authentication Required
* @param {Object} body - Call type data including id, speciesId, label
* @returns {Object} Response containing:
* - data: Created call type object
* @description Creates a new call type for a species. Requires EDIT permission on any dataset containing the species.
*/
return c.json({ error: "Not implemented yet" }, 501);
try {
const jwtPayload = (c as unknown as { jwtPayload: JWTPayload }).jwtPayload;
const userId = jwtPayload.sub;
const db = createDatabase(c.env);
// Parse and validate request body
const body = await c.req.json();
const { id, speciesId, label } = body;
// Field validation
if (!id || typeof id !== 'string' || id.length !== 12) {
return c.json({
error: "Field 'id' must be exactly 12 characters (nanoid)"
}, 400);
}
if (!speciesId || typeof speciesId !== 'string') {
return c.json({
error: "Missing or invalid required field: speciesId"
}, 400);
}
if (!label || typeof label !== 'string' || label.trim().length === 0) {
return c.json({
error: "Missing or invalid required field: label"
}, 400);
}
if (label.length > 100) {
return c.json({
error: "Field 'label' must be 100 characters or less"
}, 400);
}
// Check if species exists and get its associated datasets
const associatedDatasets = await db
.select({ datasetId: speciesDataset.datasetId })
.from(speciesDataset)
.where(eq(speciesDataset.speciesId, speciesId));
if (associatedDatasets.length === 0) {
return c.json({
error: "Species not found or not associated with any dataset"
}, 404);
}
// Check if user has EDIT permission on at least one associated dataset
let hasPermission = false;
for (const assoc of associatedDatasets) {
if (await checkUserPermission(db, userId, assoc.datasetId, 'EDIT')) {
hasPermission = true;
break;
}
}
if (!hasPermission) {
return c.json({
error: "You don't have permission to create call types for this species"
}, 403);
}
// Create call type
const now = new Date();
const [createdCallType] = await db.insert(callType).values({
id: id.trim(),
speciesId: speciesId.trim(),
label: label.trim(),
createdBy: userId,
createdAt: now,
lastModified: now,
modifiedBy: userId,
active: true,
}).returning({
id: callType.id,
speciesId: callType.speciesId,
label: callType.label,
createdAt: callType.createdAt,
active: callType.active,
});
console.log("Created call type:", createdCallType.id, "for species:", speciesId, "by user:", userId);
return c.json({
data: createdCallType
}, 201);
} catch (error) {
console.error("Error creating call type:", error);
if (error instanceof Error && error.message.includes('duplicate key')) {
return c.json({
error: "A call type with this ID already exists"
}, 400);
}
return c.json(
{
error: "Failed to create call type",
details: error instanceof Error ? error.message : String(error),
},
500
);
}
/**
* Protected API route to update an existing call type
*
* @route PUT /api/callTypes/:id
* @authentication Required
* @param {string} id - Call type ID in URL path
* @param {Object} body - Call type data to update (label, active)
* @returns {Object} Response containing:
* - data: Updated call type object
* @description Updates an existing call type. Requires EDIT permission on any dataset containing the parent species.
*/
return c.json({ error: "Not implemented yet" }, 501);
try {
const jwtPayload = (c as unknown as { jwtPayload: JWTPayload }).jwtPayload;
const userId = jwtPayload.sub;
const callTypeId = c.req.param("id");
const body = await c.req.json();
const { label, active } = body;
const db = createDatabase(c.env);
// Check if call type exists and get its species
const existingCallType = await db
.select({
id: callType.id,
speciesId: callType.speciesId,
active: callType.active
})
.from(callType)
.where(eq(callType.id, callTypeId))
.limit(1);
if (existingCallType.length === 0) {
return c.json({
error: "Call type not found"
}, 404);
}
const speciesId = existingCallType[0].speciesId;
// Get datasets associated with the species to check permissions
const associatedDatasets = await db
.select({ datasetId: speciesDataset.datasetId })
.from(speciesDataset)
.where(eq(speciesDataset.speciesId, speciesId));
// Check if user has EDIT permission on at least one associated dataset
let hasPermission = false;
for (const assoc of associatedDatasets) {
if (await checkUserPermission(db, userId, assoc.datasetId, 'EDIT')) {
hasPermission = true;
break;
}
}
if (!hasPermission) {
return c.json({
error: "You don't have permission to edit this call type"
}, 403);
}
// Build update object with only provided fields
const updateData: Record<string, unknown> = {
lastModified: new Date(),
modifiedBy: userId,
};
// Validate and add label if provided
if (label !== undefined) {
if (typeof label !== 'string' || label.trim().length === 0) {
return c.json({
error: "Field 'label' must be a non-empty string"
}, 400);
}
if (label.length > 100) {
return c.json({
error: "Field 'label' must be 100 characters or less"
}, 400);
}
updateData.label = label.trim();
}
// Add active status if provided (for soft delete)
if (active !== undefined) {
if (typeof active !== 'boolean') {
return c.json({
error: "Field 'active' must be a boolean"
}, 400);
}
updateData.active = active;
}
// Update the call type
const [updatedCallType] = await db
.update(callType)
.set(updateData)
.where(eq(callType.id, callTypeId))
.returning({
id: callType.id,
speciesId: callType.speciesId,
label: callType.label,
createdAt: callType.createdAt,
lastModified: callType.lastModified,
active: callType.active,
});
if (!updatedCallType) {
return c.json({
error: "Failed to update call type"
}, 500);
}
console.log("Updated call type:", callTypeId, "by user:", userId);
return c.json({
data: updatedCallType
});
} catch (error) {
console.error("Error updating call type:", error);
return c.json(
{
error: "Failed to update call type",
details: error instanceof Error ? error.message : String(error),
},
500
);
}