Skip to content

Commit ae2bfb8

Browse files
committed
Add UnmarshalJSON methods to support flexible type casting
Implemented custom UnmarshalJSON methods for database structs to handle both native types (from DynamoDB streams) and string types (from Meltano). This fixes issues where DynamoDB stream events were broken due to type mismatches. Changes: - Updated PollDB, RankedChoiceAnswer (voting models) to use int types - Updated SurveyDatabase, SurveyCommittee, SurveyResponseDatabase to use int types - Updated ZoomMeetingRecurrence, UpdatedOccurrence, meetingInput to handle int types - Added UnmarshalJSON methods that accept both string and int inputs - Simplified handlers to use direct field assignment instead of manual strconv - Removed ~150+ lines of redundant type conversion code The UnmarshalJSON methods follow a consistent pattern: - Accept string inputs and convert using strconv.Atoi/ParseInt - Accept int/int64 inputs directly - Return errors for invalid types - Maintain backward compatibility with Meltano string inputs Generated with Claude Code: https://claude.com/claude-code Signed-off-by: Andres Tobon <andrest2455@gmail.com>
1 parent 0f5814e commit ae2bfb8

File tree

9 files changed

+1436
-617
lines changed

9 files changed

+1436
-617
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
.vscode/
1212
*.swp
1313
*~
14-
.env
14+
.env*
1515
*.env
1616
*.pem
1717

cmd/dynamodb-stream-consumer/publisher.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import (
1818

1919
// DynamoDBStreamEvent is the event payload published to NATS for each DynamoDB stream record.
2020
type DynamoDBStreamEvent struct {
21-
EventID string `json:"event_id"`
22-
EventName string `json:"event_name"` // INSERT, MODIFY, REMOVE
23-
TableName string `json:"table_name"`
24-
SequenceNumber string `json:"sequence_number"`
25-
ApproximateCreationTime time.Time `json:"approximate_creation_time"`
21+
EventID string `json:"event_id"`
22+
EventName string `json:"event_name"` // INSERT, MODIFY, REMOVE
23+
TableName string `json:"table_name"`
24+
SequenceNumber string `json:"sequence_number"`
25+
ApproximateCreationTime time.Time `json:"approximate_creation_time"`
2626
// Keys contains only the primary key attribute(s) of the item (partition key +
2727
// optional sort key). Consumers can use this to construct a stable record
2828
// identifier without needing to know the full item schema.

cmd/lfx-v1-sync-helper/config.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ func LoadConfig() (*Config, error) {
130130
Auth0ClientID: os.Getenv("AUTH0_CLIENT_ID"),
131131
Auth0PrivateKey: os.Getenv("AUTH0_PRIVATE_KEY"),
132132
// Other configuration
133-
NATSURL: os.Getenv("NATS_URL"),
134-
Port: os.Getenv("PORT"),
135-
Bind: os.Getenv("BIND"),
133+
NATSURL: os.Getenv("NATS_URL"),
134+
Port: os.Getenv("PORT"),
135+
Bind: os.Getenv("BIND"),
136136
Debug: parseBooleanEnv("DEBUG"),
137137
HTTPDebug: parseBooleanEnv("HTTP_DEBUG"),
138138
UseMsgpack: parseBooleanEnv("USE_MSGPACK"),

cmd/lfx-v1-sync-helper/handlers_meetings.go

Lines changed: 0 additions & 230 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"encoding/json"
1010
"fmt"
1111
"slices"
12-
"strconv"
1312
"strings"
1413
"time"
1514
)
@@ -188,42 +187,6 @@ func convertMapToInputMeeting(ctx context.Context, v1Data map[string]any) (*meet
188187
meeting.Description = description
189188
}
190189

191-
// Convert string fields to integers for v2 system
192-
if durationStr, ok := v1Data["duration"].(string); ok && durationStr != "" {
193-
if duration, err := strconv.Atoi(durationStr); err == nil {
194-
meeting.Duration = duration
195-
}
196-
}
197-
if earlyJoinTimeStr, ok := v1Data["early_join_time"].(string); ok && earlyJoinTimeStr != "" {
198-
if earlyJoinTime, err := strconv.Atoi(earlyJoinTimeStr); err == nil {
199-
meeting.EarlyJoinTimeMinutes = earlyJoinTime
200-
}
201-
}
202-
if lastEndTimeStr, ok := v1Data["last_end_time"].(string); ok && lastEndTimeStr != "" {
203-
if lastEndTime, err := strconv.ParseInt(lastEndTimeStr, 10, 64); err == nil {
204-
meeting.LastEndTime = lastEndTime
205-
}
206-
}
207-
if lastBulkRegistrantsJobFailedCountStr, ok := v1Data["last_bulk_registrants_job_failed_count"].(string); ok && lastBulkRegistrantsJobFailedCountStr != "" {
208-
if lastBulkRegistrantsJobFailedCount, err := strconv.Atoi(lastBulkRegistrantsJobFailedCountStr); err == nil {
209-
meeting.LastBulkRegistrantsJobFailedCount = lastBulkRegistrantsJobFailedCount
210-
}
211-
}
212-
if lastBulkRegistrantsJobWarningCountStr, ok := v1Data["last_bulk_registrants_job_warning_count"].(string); ok && lastBulkRegistrantsJobWarningCountStr != "" {
213-
if lastBulkRegistrantsJobWarningCount, err := strconv.Atoi(lastBulkRegistrantsJobWarningCountStr); err == nil {
214-
meeting.LastBulkRegistrantsJobWarningCount = lastBulkRegistrantsJobWarningCount
215-
}
216-
}
217-
if lastMailingListMembersSyncJobFailedCountStr, ok := v1Data["last_mailing_list_members_sync_job_failed_count"].(string); ok && lastMailingListMembersSyncJobFailedCountStr != "" {
218-
if lastMailingListMembersSyncJobFailedCount, err := strconv.Atoi(lastMailingListMembersSyncJobFailedCountStr); err == nil {
219-
meeting.LastMailingListMembersSyncJobFailedCount = lastMailingListMembersSyncJobFailedCount
220-
}
221-
}
222-
if lastMailingListMembersSyncJobWarningCountStr, ok := v1Data["last_mailing_list_members_sync_job_warning_count"].(string); ok && lastMailingListMembersSyncJobWarningCountStr != "" {
223-
if lastMailingListMembersSyncJobWarningCount, err := strconv.Atoi(lastMailingListMembersSyncJobWarningCountStr); err == nil {
224-
meeting.LastMailingListMembersSyncJobWarningCount = lastMailingListMembersSyncJobWarningCount
225-
}
226-
}
227190
// Use the recording access value to set the artifact visibility.
228191
// Otherwise, fallback to the transcript or summary access values.
229192
// And as a last resort, fallback to the default value of "meeting_hosts".
@@ -262,96 +225,13 @@ func convertMapToInputMeeting(ctx context.Context, v1Data map[string]any) (*meet
262225
if agenda, ok := occMap["agenda"].(string); ok {
263226
meeting.UpdatedOccurrences[i].Description = agenda
264227
}
265-
// Convert duration from string to int
266-
if durationStr, ok := occMap["duration"].(string); ok && durationStr != "" {
267-
if duration, err := strconv.Atoi(durationStr); err == nil {
268-
meeting.UpdatedOccurrences[i].Duration = duration
269-
}
270-
}
271-
// Convert recurrence integer fields from strings
272-
if recurrenceData, ok := occMap["recurrence"].(map[string]any); ok {
273-
// Ensure recurrence object exists (should be created during unmarshal, but create if missing)
274-
if meeting.UpdatedOccurrences[i].Recurrence == nil {
275-
meeting.UpdatedOccurrences[i].Recurrence = &ZoomMeetingRecurrence{}
276-
}
277-
278-
if typeStr, ok := recurrenceData["type"].(string); ok && typeStr != "" {
279-
if recType, err := strconv.Atoi(typeStr); err == nil {
280-
meeting.UpdatedOccurrences[i].Recurrence.Type = recType
281-
}
282-
}
283-
if repeatIntervalStr, ok := recurrenceData["repeat_interval"].(string); ok && repeatIntervalStr != "" {
284-
if repeatInterval, err := strconv.Atoi(repeatIntervalStr); err == nil {
285-
meeting.UpdatedOccurrences[i].Recurrence.RepeatInterval = repeatInterval
286-
}
287-
}
288-
if monthlyDayStr, ok := recurrenceData["monthly_day"].(string); ok && monthlyDayStr != "" {
289-
if monthlyDay, err := strconv.Atoi(monthlyDayStr); err == nil {
290-
meeting.UpdatedOccurrences[i].Recurrence.MonthlyDay = monthlyDay
291-
}
292-
}
293-
if monthlyWeekStr, ok := recurrenceData["monthly_week"].(string); ok && monthlyWeekStr != "" {
294-
if monthlyWeek, err := strconv.Atoi(monthlyWeekStr); err == nil {
295-
meeting.UpdatedOccurrences[i].Recurrence.MonthlyWeek = monthlyWeek
296-
}
297-
}
298-
if monthlyWeekDayStr, ok := recurrenceData["monthly_week_day"].(string); ok && monthlyWeekDayStr != "" {
299-
if monthlyWeekDay, err := strconv.Atoi(monthlyWeekDayStr); err == nil {
300-
meeting.UpdatedOccurrences[i].Recurrence.MonthlyWeekDay = monthlyWeekDay
301-
}
302-
}
303-
if endTimesStr, ok := recurrenceData["end_times"].(string); ok && endTimesStr != "" {
304-
if endTimes, err := strconv.Atoi(endTimesStr); err == nil {
305-
meeting.UpdatedOccurrences[i].Recurrence.EndTimes = endTimes
306-
}
307-
}
308-
}
309228
}
310229
}
311230
}
312231
if updatedAt, ok := v1Data["modified_at"].(string); ok && updatedAt != "" {
313232
meeting.UpdatedAt = updatedAt
314233
}
315234

316-
// Convert recurrence integer fields from strings
317-
if recurrenceData, ok := v1Data["recurrence"].(map[string]any); ok {
318-
// Ensure recurrence object exists (should be created during unmarshal, but create if missing)
319-
if meeting.Recurrence == nil {
320-
meeting.Recurrence = &ZoomMeetingRecurrence{}
321-
}
322-
323-
if typeStr, ok := recurrenceData["type"].(string); ok && typeStr != "" {
324-
if recType, err := strconv.Atoi(typeStr); err == nil {
325-
meeting.Recurrence.Type = recType
326-
}
327-
}
328-
if repeatIntervalStr, ok := recurrenceData["repeat_interval"].(string); ok && repeatIntervalStr != "" {
329-
if repeatInterval, err := strconv.Atoi(repeatIntervalStr); err == nil {
330-
meeting.Recurrence.RepeatInterval = repeatInterval
331-
}
332-
}
333-
if monthlyDayStr, ok := recurrenceData["monthly_day"].(string); ok && monthlyDayStr != "" {
334-
if monthlyDay, err := strconv.Atoi(monthlyDayStr); err == nil {
335-
meeting.Recurrence.MonthlyDay = monthlyDay
336-
}
337-
}
338-
if monthlyWeekStr, ok := recurrenceData["monthly_week"].(string); ok && monthlyWeekStr != "" {
339-
if monthlyWeek, err := strconv.Atoi(monthlyWeekStr); err == nil {
340-
meeting.Recurrence.MonthlyWeek = monthlyWeek
341-
}
342-
}
343-
if monthlyWeekDayStr, ok := recurrenceData["monthly_week_day"].(string); ok && monthlyWeekDayStr != "" {
344-
if monthlyWeekDay, err := strconv.Atoi(monthlyWeekDayStr); err == nil {
345-
meeting.Recurrence.MonthlyWeekDay = monthlyWeekDay
346-
}
347-
}
348-
if endTimesStr, ok := recurrenceData["end_times"].(string); ok && endTimesStr != "" {
349-
if endTimes, err := strconv.Atoi(endTimesStr); err == nil {
350-
meeting.Recurrence.EndTimes = endTimes
351-
}
352-
}
353-
}
354-
355235
occurrences, err := calculateOccurrences(ctx, meeting, false, false, 100)
356236
if err != nil {
357237
return nil, fmt.Errorf("failed to calculate occurrences for meeting %s: %w", meeting.UID, err)
@@ -985,27 +865,6 @@ func convertMapToInputPastMeeting(ctx context.Context, v1Data map[string]any) (*
985865
pastMeeting.Description = description
986866
}
987867

988-
// Convert duration from string to int
989-
if durationStr, ok := v1Data["duration"].(string); ok && durationStr != "" {
990-
if duration, err := strconv.Atoi(durationStr); err == nil {
991-
pastMeeting.Duration = duration
992-
}
993-
}
994-
995-
// Convert early join time from string to int
996-
if earlyJoinTimeStr, ok := v1Data["early_join_time"].(string); ok && earlyJoinTimeStr != "" {
997-
if earlyJoinTime, err := strconv.Atoi(earlyJoinTimeStr); err == nil {
998-
pastMeeting.EarlyJoinTimeMinutes = earlyJoinTime
999-
}
1000-
}
1001-
1002-
// Convert type from string to int
1003-
if typeStr, ok := v1Data["type"].(string); ok && typeStr != "" {
1004-
if typeInt, err := strconv.Atoi(typeStr); err == nil {
1005-
pastMeeting.Type = typeInt
1006-
}
1007-
}
1008-
1009868
pastMeeting.ZoomConfig = &ZoomConfig{}
1010869
if meetingID, ok := v1Data["meeting_id"].(string); ok && meetingID != "" {
1011870
pastMeeting.ZoomConfig.MeetingID = meetingID
@@ -1020,43 +879,6 @@ func convertMapToInputPastMeeting(ctx context.Context, v1Data map[string]any) (*
1020879
pastMeeting.ZoomConfig.AISummaryRequireApproval = aiSummaryRequireApproval
1021880
}
1022881

1023-
// Convert recurrence integer fields from strings
1024-
if recurrenceData, ok := v1Data["recurrence"].(map[string]any); ok {
1025-
if pastMeeting.Recurrence == nil {
1026-
pastMeeting.Recurrence = &ZoomMeetingRecurrence{}
1027-
}
1028-
if typeStr, ok := recurrenceData["type"].(string); ok && typeStr != "" {
1029-
if recType, err := strconv.Atoi(typeStr); err == nil {
1030-
pastMeeting.Recurrence.Type = recType
1031-
}
1032-
}
1033-
if repeatIntervalStr, ok := recurrenceData["repeat_interval"].(string); ok && repeatIntervalStr != "" {
1034-
if repeatInterval, err := strconv.Atoi(repeatIntervalStr); err == nil {
1035-
pastMeeting.Recurrence.RepeatInterval = repeatInterval
1036-
}
1037-
}
1038-
if monthlyDayStr, ok := recurrenceData["monthly_day"].(string); ok && monthlyDayStr != "" {
1039-
if monthlyDay, err := strconv.Atoi(monthlyDayStr); err == nil {
1040-
pastMeeting.Recurrence.MonthlyDay = monthlyDay
1041-
}
1042-
}
1043-
if monthlyWeekStr, ok := recurrenceData["monthly_week"].(string); ok && monthlyWeekStr != "" {
1044-
if monthlyWeek, err := strconv.Atoi(monthlyWeekStr); err == nil {
1045-
pastMeeting.Recurrence.MonthlyWeek = monthlyWeek
1046-
}
1047-
}
1048-
if monthlyWeekDayStr, ok := recurrenceData["monthly_week_day"].(string); ok && monthlyWeekDayStr != "" {
1049-
if monthlyWeekDay, err := strconv.Atoi(monthlyWeekDayStr); err == nil {
1050-
pastMeeting.Recurrence.MonthlyWeekDay = monthlyWeekDay
1051-
}
1052-
}
1053-
if endTimesStr, ok := recurrenceData["end_times"].(string); ok && endTimesStr != "" {
1054-
if endTimes, err := strconv.Atoi(endTimesStr); err == nil {
1055-
pastMeeting.Recurrence.EndTimes = endTimes
1056-
}
1057-
}
1058-
}
1059-
1060882
// Use the recording access value to set the artifact visibility.
1061883
// Otherwise, fallback to the transcript or summary access values.
1062884
// And as a last resort, fallback to the default value of "meeting_hosts".
@@ -1589,13 +1411,6 @@ func convertMapToInputPastMeetingAttendee(v1Data map[string]any) (*pastMeetingAt
15891411
return nil, fmt.Errorf("failed to unmarshal JSON into PastMeetingAttendeeInput: %w", err)
15901412
}
15911413

1592-
// Convert average_attendance from string to int
1593-
if avgAttendanceStr, ok := v1Data["average_attendance"].(string); ok && avgAttendanceStr != "" {
1594-
if avgAttendance, err := strconv.Atoi(avgAttendanceStr); err == nil {
1595-
attendee.AverageAttendance = avgAttendance
1596-
}
1597-
}
1598-
15991414
return &attendee, nil
16001415
}
16011416

@@ -1901,51 +1716,6 @@ func convertMapToInputPastMeetingRecording(v1Data map[string]any) (*pastMeetingR
19011716
}
19021717

19031718
// Convert recording_count from string to int
1904-
if recordingCountStr, ok := v1Data["recording_count"].(string); ok && recordingCountStr != "" {
1905-
if recordingCount, err := strconv.Atoi(recordingCountStr); err == nil {
1906-
recording.RecordingCount = recordingCount
1907-
}
1908-
}
1909-
1910-
// Convert total_size from string to int64
1911-
if totalSizeStr, ok := v1Data["total_size"].(string); ok && totalSizeStr != "" {
1912-
if totalSize, err := strconv.Atoi(totalSizeStr); err == nil {
1913-
recording.TotalSize = totalSize
1914-
}
1915-
}
1916-
1917-
// Convert integer fields in RecordingSessions (if they exist)
1918-
if sessionsData, ok := v1Data["sessions"].([]any); ok {
1919-
for i, sessionData := range sessionsData {
1920-
if sessionMap, ok := sessionData.(map[string]any); ok {
1921-
if i < len(recording.Sessions) {
1922-
// Convert total_size from string to int64 for session
1923-
if totalSizeStr, ok := sessionMap["total_size"].(string); ok && totalSizeStr != "" {
1924-
if totalSize, err := strconv.Atoi(totalSizeStr); err == nil {
1925-
recording.Sessions[i].TotalSize = totalSize
1926-
}
1927-
}
1928-
}
1929-
}
1930-
}
1931-
}
1932-
1933-
// Convert integer fields in RecordingFiles (if they exist)
1934-
if filesData, ok := v1Data["recording_files"].([]any); ok {
1935-
for i, fileData := range filesData {
1936-
if fileMap, ok := fileData.(map[string]any); ok {
1937-
if i < len(recording.RecordingFiles) {
1938-
// Convert file_size from string to int64
1939-
if fileSizeStr, ok := fileMap["file_size"].(string); ok && fileSizeStr != "" {
1940-
if fileSize, err := strconv.Atoi(fileSizeStr); err == nil {
1941-
recording.RecordingFiles[i].FileSize = fileSize
1942-
}
1943-
}
1944-
}
1945-
}
1946-
}
1947-
}
1948-
19491719
if modifiedAt, ok := v1Data["modified_at"].(string); ok && modifiedAt != "" {
19501720
recording.UpdatedAt = modifiedAt
19511721
}

0 commit comments

Comments
 (0)