working on timeline locations
This commit is contained in:
+78
-5
@@ -286,6 +286,19 @@ const birdProfileListSchema = z
|
||||
.optional()
|
||||
.or(z.literal(''));
|
||||
|
||||
const verifiedLocationDetailsSchema = z
|
||||
.object({
|
||||
city: z.string().trim().max(120).optional().or(z.literal('')),
|
||||
region: z.string().trim().max(120).optional().or(z.literal('')),
|
||||
country: z.string().trim().max(120).optional().or(z.literal('')),
|
||||
countryCode: z.string().trim().max(2).optional().or(z.literal('')),
|
||||
latitude: z.coerce.number().min(-90).max(90).optional().nullable().or(z.literal('')),
|
||||
longitude: z.coerce.number().min(-180).max(180).optional().nullable().or(z.literal('')),
|
||||
precision: z.enum(['city', 'region', 'country']).optional(),
|
||||
})
|
||||
.optional()
|
||||
.nullable();
|
||||
|
||||
const birdSchema = z.object({
|
||||
name: z.string().trim().min(1).max(120),
|
||||
tagId: z.string().trim().max(80).optional().or(z.literal('')),
|
||||
@@ -294,6 +307,7 @@ const birdSchema = z.object({
|
||||
demotivators: birdProfileListSchema,
|
||||
favoriteSnack: z.string().trim().max(160).optional().or(z.literal('')),
|
||||
locationLabel: z.string().trim().max(160).optional().or(z.literal('')),
|
||||
locationDetails: verifiedLocationDetailsSchema,
|
||||
vetClinicName: z.string().trim().max(160).optional().or(z.literal('')),
|
||||
vetClinicAddress: z.string().trim().max(500).optional().or(z.literal('')),
|
||||
vetAccountNumber: z.string().trim().max(120).optional().or(z.literal('')),
|
||||
@@ -323,13 +337,56 @@ const birdTimelineEventSchema = z
|
||||
eventType: z.enum(['location_updated', 'owner_changed', 'manual_note']),
|
||||
eventDate: dateStringSchema.optional().or(z.literal('')),
|
||||
locationLabel: z.string().trim().max(160).optional().or(z.literal('')),
|
||||
locationDetails: verifiedLocationDetailsSchema,
|
||||
note: z.string().trim().max(500).optional().or(z.literal('')),
|
||||
})
|
||||
.refine(
|
||||
(value) => value.eventType === 'owner_changed' || Boolean(value.locationLabel?.trim() || value.note?.trim()),
|
||||
(value) =>
|
||||
value.eventType === 'owner_changed' ||
|
||||
Boolean(
|
||||
value.locationLabel?.trim() ||
|
||||
value.note?.trim() ||
|
||||
value.locationDetails?.city?.trim() ||
|
||||
value.locationDetails?.region?.trim() ||
|
||||
value.locationDetails?.country?.trim(),
|
||||
),
|
||||
'Add a location or note for this timeline item.',
|
||||
);
|
||||
|
||||
type VerifiedLocationDetailsInput = z.infer<typeof verifiedLocationDetailsSchema>;
|
||||
|
||||
const normalizeVerifiedLocationDetails = (value: VerifiedLocationDetailsInput) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const city = value.city?.trim() || null;
|
||||
const region = value.region?.trim() || null;
|
||||
const country = value.country?.trim() || null;
|
||||
const countryCode = value.countryCode?.trim().toUpperCase() || null;
|
||||
const latitude = typeof value.latitude === 'number' ? Number(value.latitude.toFixed(4)) : null;
|
||||
const longitude = typeof value.longitude === 'number' ? Number(value.longitude.toFixed(4)) : null;
|
||||
const precision = value.precision ?? (city ? 'city' : region ? 'region' : country ? 'country' : null);
|
||||
|
||||
if (!city && !region && !country && !countryCode && latitude === null && longitude === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
city,
|
||||
region,
|
||||
country,
|
||||
countryCode,
|
||||
latitude,
|
||||
longitude,
|
||||
precision,
|
||||
verifiedAt: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
const formatVerifiedLocationLabel = (details: ReturnType<typeof normalizeVerifiedLocationDetails>) =>
|
||||
details ? [details.city, details.region, details.country].filter(Boolean).join(', ') || null : null;
|
||||
|
||||
const weightSchema = z.object({
|
||||
weightGrams: z.coerce.number().positive().max(10000),
|
||||
recordedOn: dateStringSchema,
|
||||
@@ -707,6 +764,7 @@ const normalizeBird = (row: BirdRow) => ({
|
||||
demotivators: row.demotivators,
|
||||
favoriteSnack: row.favorite_snack,
|
||||
locationLabel: row.location_label,
|
||||
locationDetails: row.location_details,
|
||||
vetClinicName: row.vet_clinic_name,
|
||||
vetClinicAddress: row.vet_clinic_address,
|
||||
vetAccountNumber: row.vet_account_number,
|
||||
@@ -825,6 +883,7 @@ const normalizeBirdTimelineEvent = (row: BirdTimelineEventRow) => ({
|
||||
fromOwnerEmail: row.from_owner_email,
|
||||
toOwnerEmail: row.to_owner_email,
|
||||
locationLabel: row.location_label,
|
||||
locationDetails: row.location_details,
|
||||
note: row.note,
|
||||
eventDate: row.event_date,
|
||||
createdByUserId: row.created_by_user_id,
|
||||
@@ -2367,6 +2426,7 @@ const writeBirdTimelineEvent = async ({
|
||||
fromWorkspaceId,
|
||||
toWorkspaceId,
|
||||
locationLabel,
|
||||
locationDetails,
|
||||
note,
|
||||
eventDate,
|
||||
createdByUserId,
|
||||
@@ -2376,6 +2436,7 @@ const writeBirdTimelineEvent = async ({
|
||||
fromWorkspaceId?: number | null;
|
||||
toWorkspaceId?: number | null;
|
||||
locationLabel?: string | null;
|
||||
locationDetails?: Record<string, unknown> | null;
|
||||
note?: string | null;
|
||||
eventDate?: string | null;
|
||||
createdByUserId?: string | null;
|
||||
@@ -2387,6 +2448,7 @@ const writeBirdTimelineEvent = async ({
|
||||
fromWorkspaceId,
|
||||
toWorkspaceId,
|
||||
locationLabel,
|
||||
locationDetails,
|
||||
note,
|
||||
eventDate,
|
||||
createdByUserId,
|
||||
@@ -3644,11 +3706,13 @@ app.post(
|
||||
return;
|
||||
}
|
||||
|
||||
const locationDetails = normalizeVerifiedLocationDetails(parsed.data.locationDetails);
|
||||
const event = await createBirdTimelineEvent({
|
||||
birdId: bird.id,
|
||||
eventType: parsed.data.eventType as BirdTimelineEventType,
|
||||
toWorkspaceId: req.auth!.workspace.id,
|
||||
locationLabel: emptyToNull(parsed.data.locationLabel),
|
||||
locationLabel: formatVerifiedLocationLabel(locationDetails) ?? emptyToNull(parsed.data.locationLabel),
|
||||
locationDetails,
|
||||
note: emptyToNull(parsed.data.note),
|
||||
eventDate: emptyToNull(parsed.data.eventDate),
|
||||
createdByUserId: req.auth!.user.id,
|
||||
@@ -3742,6 +3806,7 @@ app.post('/api/birds', requireAuth, requireWriteAccess, requireWorkspaceRole(['o
|
||||
workspaceId: req.auth!.workspace.id,
|
||||
photoDataUrl: emptyToNull(parsed.data.photoDataUrl),
|
||||
});
|
||||
const locationDetails = normalizeVerifiedLocationDetails(parsed.data.locationDetails);
|
||||
uploadedObjectKeyToCleanup = photoStorage.photoObjectKey;
|
||||
const bird = await createBird({
|
||||
birdId,
|
||||
@@ -3752,7 +3817,8 @@ app.post('/api/birds', requireAuth, requireWriteAccess, requireWorkspaceRole(['o
|
||||
motivators: emptyToNull(parsed.data.motivators),
|
||||
demotivators: emptyToNull(parsed.data.demotivators),
|
||||
favoriteSnack: emptyToNull(parsed.data.favoriteSnack),
|
||||
locationLabel: emptyToNull(parsed.data.locationLabel),
|
||||
locationLabel: formatVerifiedLocationLabel(locationDetails) ?? emptyToNull(parsed.data.locationLabel),
|
||||
locationDetails,
|
||||
vetClinicName: emptyToNull(parsed.data.vetClinicName),
|
||||
vetClinicAddress: emptyToNull(parsed.data.vetClinicAddress),
|
||||
vetAccountNumber: emptyToNull(parsed.data.vetAccountNumber),
|
||||
@@ -3781,6 +3847,7 @@ app.post('/api/birds', requireAuth, requireWriteAccess, requireWorkspaceRole(['o
|
||||
eventType: 'profile_created',
|
||||
toWorkspaceId: req.auth!.workspace.id,
|
||||
locationLabel: bird!.location_label,
|
||||
locationDetails: bird!.location_details,
|
||||
createdByUserId: req.auth!.user.id,
|
||||
});
|
||||
res.status(201).json({ bird: normalizeBird(bird!) });
|
||||
@@ -4109,6 +4176,7 @@ app.put('/api/birds/:birdId', requireAuth, requireWriteAccess, requireWorkspaceR
|
||||
});
|
||||
uploadedObjectKeyToCleanup =
|
||||
photoStorage.photoObjectKey && photoStorage.photoObjectKey !== existingBird.photo_object_key ? photoStorage.photoObjectKey : null;
|
||||
const locationDetails = normalizeVerifiedLocationDetails(parsed.data.locationDetails);
|
||||
const bird = await updateBird({
|
||||
birdId: req.params.birdId,
|
||||
workspaceId: req.auth!.workspace.id,
|
||||
@@ -4118,7 +4186,8 @@ app.put('/api/birds/:birdId', requireAuth, requireWriteAccess, requireWorkspaceR
|
||||
motivators: emptyToNull(parsed.data.motivators),
|
||||
demotivators: emptyToNull(parsed.data.demotivators),
|
||||
favoriteSnack: emptyToNull(parsed.data.favoriteSnack),
|
||||
locationLabel: emptyToNull(parsed.data.locationLabel),
|
||||
locationLabel: formatVerifiedLocationLabel(locationDetails) ?? emptyToNull(parsed.data.locationLabel),
|
||||
locationDetails,
|
||||
vetClinicName: emptyToNull(parsed.data.vetClinicName),
|
||||
vetClinicAddress: emptyToNull(parsed.data.vetClinicAddress),
|
||||
vetAccountNumber: emptyToNull(parsed.data.vetAccountNumber),
|
||||
@@ -4148,12 +4217,16 @@ app.put('/api/birds/:birdId', requireAuth, requireWriteAccess, requireWorkspaceR
|
||||
previousName: existingBird.name,
|
||||
species: bird.species,
|
||||
});
|
||||
if ((existingBird.location_label ?? '') !== (bird.location_label ?? '')) {
|
||||
if (
|
||||
(existingBird.location_label ?? '') !== (bird.location_label ?? '') ||
|
||||
JSON.stringify(existingBird.location_details ?? null) !== JSON.stringify(bird.location_details ?? null)
|
||||
) {
|
||||
await writeBirdTimelineEvent({
|
||||
birdId: bird.id,
|
||||
eventType: 'location_updated',
|
||||
toWorkspaceId: req.auth!.workspace.id,
|
||||
locationLabel: bird.location_label,
|
||||
locationDetails: bird.location_details,
|
||||
createdByUserId: req.auth!.user.id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
|
||||
demotivators VARCHAR(1000),
|
||||
favorite_snack VARCHAR(160),
|
||||
location_label VARCHAR(160),
|
||||
location_details JSONB,
|
||||
vet_clinic_name VARCHAR(160),
|
||||
vet_clinic_address VARCHAR(500),
|
||||
vet_account_number VARCHAR(120),
|
||||
@@ -279,6 +280,7 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
|
||||
ADD COLUMN IF NOT EXISTS demotivators VARCHAR(1000),
|
||||
ADD COLUMN IF NOT EXISTS favorite_snack VARCHAR(160),
|
||||
ADD COLUMN IF NOT EXISTS location_label VARCHAR(160),
|
||||
ADD COLUMN IF NOT EXISTS location_details JSONB,
|
||||
ADD COLUMN IF NOT EXISTS vet_clinic_name VARCHAR(160),
|
||||
ADD COLUMN IF NOT EXISTS vet_clinic_address VARCHAR(500),
|
||||
ADD COLUMN IF NOT EXISTS vet_account_number VARCHAR(120),
|
||||
@@ -415,6 +417,7 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
|
||||
from_owner_email VARCHAR(255),
|
||||
to_owner_email VARCHAR(255),
|
||||
location_label VARCHAR(160),
|
||||
location_details JSONB,
|
||||
note TEXT,
|
||||
event_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
@@ -423,6 +426,7 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
|
||||
|
||||
ALTER TABLE bird_timeline_events
|
||||
ADD COLUMN IF NOT EXISTS note TEXT,
|
||||
ADD COLUMN IF NOT EXISTS location_details JSONB,
|
||||
ADD COLUMN IF NOT EXISTS event_date DATE NOT NULL DEFAULT CURRENT_DATE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bird_timeline_events_bird_created
|
||||
|
||||
@@ -222,6 +222,7 @@ test('completePendingBirdTransfersForOwner moves pending birds and marks complet
|
||||
from_owner_email: 'sender@example.com',
|
||||
to_owner_email: 'receiver@example.com',
|
||||
location_label: 'Receiving Flock',
|
||||
location_details: null,
|
||||
created_by_user_id: 'user-1',
|
||||
created_at: '2026-04-15T00:00:00.000Z',
|
||||
},
|
||||
@@ -249,5 +250,6 @@ test('completePendingBirdTransfersForOwner moves pending birds and marks complet
|
||||
null,
|
||||
null,
|
||||
'user-1',
|
||||
null,
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ const birdSelectFields = `
|
||||
birds.demotivators,
|
||||
birds.favorite_snack,
|
||||
birds.location_label,
|
||||
birds.location_details,
|
||||
birds.vet_clinic_name,
|
||||
birds.vet_clinic_address,
|
||||
birds.vet_account_number,
|
||||
@@ -171,6 +172,7 @@ export const createBirdTimelineEvent = async ({
|
||||
fromWorkspaceId,
|
||||
toWorkspaceId,
|
||||
locationLabel,
|
||||
locationDetails,
|
||||
note,
|
||||
eventDate,
|
||||
createdByUserId,
|
||||
@@ -180,6 +182,7 @@ export const createBirdTimelineEvent = async ({
|
||||
fromWorkspaceId?: number | null;
|
||||
toWorkspaceId?: number | null;
|
||||
locationLabel?: string | null;
|
||||
locationDetails?: Record<string, unknown> | null;
|
||||
note?: string | null;
|
||||
eventDate?: string | null;
|
||||
createdByUserId?: string | null;
|
||||
@@ -202,10 +205,11 @@ export const createBirdTimelineEvent = async ({
|
||||
location_label,
|
||||
note,
|
||||
event_date,
|
||||
created_by_user_id
|
||||
created_by_user_id,
|
||||
location_details
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, COALESCE($9, $6, $5), $10, COALESCE($11::date, CURRENT_DATE), $12)
|
||||
RETURNING id, bird_id, event_type, from_workspace_id, to_workspace_id, from_workspace_name, to_workspace_name, from_owner_email, to_owner_email, location_label, note, event_date::text, created_by_user_id, created_at`,
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, COALESCE($9::text, $6::text, $5::text), $10, COALESCE($11::date, CURRENT_DATE), $12, $13)
|
||||
RETURNING id, bird_id, event_type, from_workspace_id, to_workspace_id, from_workspace_name, to_workspace_name, from_owner_email, to_owner_email, location_label, location_details, note, event_date::text, created_by_user_id, created_at`,
|
||||
[
|
||||
birdId,
|
||||
eventType,
|
||||
@@ -219,6 +223,7 @@ export const createBirdTimelineEvent = async ({
|
||||
note ?? null,
|
||||
eventDate ?? null,
|
||||
createdByUserId ?? null,
|
||||
locationDetails ?? null,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -227,7 +232,7 @@ export const createBirdTimelineEvent = async ({
|
||||
|
||||
export const listBirdTimelineEvents = async (birdId: string, workspaceId: number) => {
|
||||
const result = await db.query<BirdTimelineEventRow>(
|
||||
`SELECT id, bird_id, event_type, from_workspace_id, to_workspace_id, from_workspace_name, to_workspace_name, from_owner_email, to_owner_email, location_label, note, event_date::text, created_by_user_id, created_at
|
||||
`SELECT id, bird_id, event_type, from_workspace_id, to_workspace_id, from_workspace_name, to_workspace_name, from_owner_email, to_owner_email, location_label, location_details, note, event_date::text, created_by_user_id, created_at
|
||||
FROM bird_timeline_events
|
||||
WHERE bird_id = $1
|
||||
AND EXISTS (
|
||||
@@ -477,6 +482,7 @@ export const createBird = async ({
|
||||
demotivators,
|
||||
favoriteSnack,
|
||||
locationLabel = null,
|
||||
locationDetails = null,
|
||||
vetClinicName = null,
|
||||
vetClinicAddress = null,
|
||||
vetAccountNumber = null,
|
||||
@@ -503,6 +509,7 @@ export const createBird = async ({
|
||||
demotivators: string | null;
|
||||
favoriteSnack: string | null;
|
||||
locationLabel?: string | null;
|
||||
locationDetails?: Record<string, unknown> | null;
|
||||
vetClinicName?: string | null;
|
||||
vetClinicAddress?: string | null;
|
||||
vetAccountNumber?: string | null;
|
||||
@@ -521,9 +528,9 @@ export const createBird = async ({
|
||||
publicProfileEnabled?: boolean;
|
||||
}) => {
|
||||
const result = await db.query<BirdRow>(
|
||||
`INSERT INTO birds (id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth, gotcha_day, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled)
|
||||
VALUES (COALESCE($1::uuid, gen_random_uuid()), $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at, NULL::text AS latest_weight_grams, NULL::text AS latest_recorded_on`,
|
||||
`INSERT INTO birds (id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, location_details, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth, gotcha_day, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled)
|
||||
VALUES (COALESCE($1::uuid, gen_random_uuid()), $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26)
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, location_details, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at, NULL::text AS latest_weight_grams, NULL::text AS latest_recorded_on`,
|
||||
[
|
||||
birdId ?? null,
|
||||
workspaceId,
|
||||
@@ -534,6 +541,7 @@ export const createBird = async ({
|
||||
demotivators,
|
||||
favoriteSnack,
|
||||
locationLabel,
|
||||
locationDetails,
|
||||
vetClinicName,
|
||||
vetClinicAddress,
|
||||
vetAccountNumber,
|
||||
@@ -566,6 +574,7 @@ export const updateBird = async ({
|
||||
demotivators,
|
||||
favoriteSnack,
|
||||
locationLabel,
|
||||
locationDetails,
|
||||
vetClinicName,
|
||||
vetClinicAddress,
|
||||
vetAccountNumber,
|
||||
@@ -592,6 +601,7 @@ export const updateBird = async ({
|
||||
demotivators: string | null;
|
||||
favoriteSnack: string | null;
|
||||
locationLabel: string | null;
|
||||
locationDetails?: Record<string, unknown> | null;
|
||||
vetClinicName: string | null;
|
||||
vetClinicAddress: string | null;
|
||||
vetAccountNumber: string | null;
|
||||
@@ -633,11 +643,12 @@ export const updateBird = async ({
|
||||
notify_on_dob = $21,
|
||||
notify_on_gotcha_day = $22,
|
||||
public_profile_code = $23,
|
||||
public_profile_enabled = $24
|
||||
public_profile_enabled = $24,
|
||||
location_details = $25
|
||||
WHERE id = $1
|
||||
AND workspace_id = $25
|
||||
AND workspace_id = $26
|
||||
AND memorialized_at IS NULL
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, location_details, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
|
||||
(
|
||||
SELECT weight_grams::text
|
||||
FROM weight_records
|
||||
@@ -677,6 +688,7 @@ export const updateBird = async ({
|
||||
notifyOnGotchaDay,
|
||||
publicProfileCode,
|
||||
publicProfileEnabled,
|
||||
locationDetails ?? null,
|
||||
workspaceId,
|
||||
],
|
||||
);
|
||||
@@ -706,7 +718,7 @@ export const memorializeBird = async ({
|
||||
WHERE id = $1
|
||||
AND workspace_id = $2
|
||||
AND memorialized_at IS NULL
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, location_details, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
|
||||
(
|
||||
SELECT weight_grams::text
|
||||
FROM weight_records
|
||||
@@ -742,7 +754,7 @@ export const updateMemorialReminderPreference = async ({
|
||||
WHERE id = $1
|
||||
AND workspace_id = $2
|
||||
AND memorialized_at IS NOT NULL
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, location_details, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
|
||||
(
|
||||
SELECT weight_grams::text
|
||||
FROM weight_records
|
||||
@@ -782,7 +794,7 @@ export const transferBirdToWorkspace = async (birdId: string, sourceWorkspaceId:
|
||||
WHERE id = $1
|
||||
AND workspace_id = $2
|
||||
AND memorialized_at IS NULL
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
|
||||
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, location_label, location_details, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
|
||||
(
|
||||
SELECT weight_grams::text
|
||||
FROM weight_records
|
||||
|
||||
@@ -131,6 +131,7 @@ export type BirdRow = {
|
||||
demotivators: string | null;
|
||||
favorite_snack: string | null;
|
||||
location_label: string | null;
|
||||
location_details: Record<string, unknown> | null;
|
||||
vet_clinic_name: string | null;
|
||||
vet_clinic_address: string | null;
|
||||
vet_account_number: string | null;
|
||||
@@ -217,6 +218,7 @@ export type BirdTimelineEventRow = {
|
||||
from_owner_email: string | null;
|
||||
to_owner_email: string | null;
|
||||
location_label: string | null;
|
||||
location_details: Record<string, unknown> | null;
|
||||
note: string | null;
|
||||
event_date: string;
|
||||
created_by_user_id: string | null;
|
||||
|
||||
Reference in New Issue
Block a user