Skip to content

Commit

Permalink
Merge pull request #14 from yamitzky/feat/private-calendar
Browse files Browse the repository at this point in the history
Feat/private calendar
  • Loading branch information
yamitzky authored Feb 13, 2025
2 parents fa90ded + 2b78199 commit c5d6208
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 29 deletions.
46 changes: 38 additions & 8 deletions apps/web/app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,46 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
const minDate = subDays(startDate, 7)
const maxDate = addDays(endDate, 7)

const calendarRepositories = config.CALENDAR_IDS.map((id) => ({
id,
repository: getCalendarRepository(id),
}))
const calendarRepositories = [
...config.CALENDAR_IDS.map((id) => ({
id,
isPrivate: false,
repository: getCalendarRepository(id),
})),
...config.PRIVATE_CALENDAR_IDS.map((id) => ({
id,
isPrivate: true,
repository: getCalendarRepository(id),
})),
]
const groupRepository = getGroupRepository()
const calendars = await Promise.all(
calendarRepositories.map(async ({ id, repository }) => ({
calendarId: id,
events: await getEvents({ calendarRepository: repository, groupRepository, minDate, maxDate }),
})),
calendarRepositories.map(async ({ id, isPrivate, repository }) => {
if (!isPrivate) {
const events = await getEvents({ calendarRepository: repository, groupRepository, minDate, maxDate })
return {
calendarId: id,
events,
}
} else if (user?.email) {
const events = await getEvents({
calendarRepository: repository,
groupRepository,
minDate,
maxDate,
attendeeEmail: user.email,
})
return {
calendarId: id,
events,
}
} else {
return {
calendarId: id,
events: [],
}
}
}),
)
return json({
calendars,
Expand Down
7 changes: 6 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
"bin": {
"synk-cal": "./bin/server.js"
},
"files": ["bin", "public", "build"],
"files": [
"bin",
"public",
"build"
],
"scripts": {
"build": "remix vite:build",
"dev": "remix vite:dev",
Expand Down Expand Up @@ -50,6 +54,7 @@
"devDependencies": {
"@chromatic-com/storybook": "^3.2.2",
"@remix-run/dev": "^2.14.0",
"@remix-run/testing": "^2.15.3",
"@storybook/addon-essentials": "^8.4.5",
"@storybook/addon-interactions": "^8.4.5",
"@storybook/addon-onboarding": "^8.4.5",
Expand Down
114 changes: 114 additions & 0 deletions apps/web/tests/_index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { getEvents } from '@synk-cal/usecase'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { loader } from '../app/routes/_index'

vi.mock('@synk-cal/usecase', () => ({
getEvents: vi.fn(),
}))

vi.mock('@synk-cal/core', () => ({
config: {
CALENDAR_IDS: ['public_calendar_id'],
PRIVATE_CALENDAR_IDS: ['private_calendar_id'],
},
}))

// getAuthRepository のモックを変数として定義
const mockGetUserFromHeader = vi.fn()
vi.mock('~/services/getAuthRepository', () => ({
getAuthRepository: vi.fn(() => ({
getUserFromHeader: mockGetUserFromHeader,
})),
}))

vi.mock('~/services/getCalendarRepository', () => ({
getCalendarRepository: vi.fn(() => ({
getEvents: vi.fn(),
})),
}))

vi.mock('~/services/getGroupRepository', () => ({
getGroupRepository: vi.fn(),
}))

describe('_index', () => {
beforeEach(() => {
vi.resetAllMocks()
mockGetUserFromHeader.mockResolvedValue(undefined)
})

afterEach(() => {
vi.restoreAllMocks()
})

describe('loader', () => {
it('should load events with default date range', async () => {
const request = new Request('http://localhost/')
vi.mocked(getEvents).mockResolvedValue([])

const response = await loader({ request, params: {}, context: {} })
const data = await response.json()

expect(data).toEqual({
calendars: [
{ calendarId: 'public_calendar_id', events: [] },
{ calendarId: 'private_calendar_id', events: [] },
],
isMobile: false,
startDate: expect.any(String),
endDate: expect.any(String),
user: undefined,
})
})

it('should load events with specified date range', async () => {
const request = new Request('http://localhost/?startDate=2025-02-14&endDate=2025-02-21')
vi.mocked(getEvents).mockResolvedValue([])

const response = await loader({ request, params: {}, context: {} })
const data = await response.json()

expect(data).toEqual({
calendars: [
{ calendarId: 'public_calendar_id', events: [] },
{ calendarId: 'private_calendar_id', events: [] },
],
isMobile: false,
startDate: '2025-02-14',
endDate: '2025-02-21',
user: undefined,
})
})

it('should detect mobile user agent', async () => {
const request = new Request('http://localhost/', {
headers: {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) Mobile/123',
},
})
vi.mocked(getEvents).mockResolvedValue([])

const response = await loader({ request, params: {}, context: {} })
const data = await response.json()

expect(data.isMobile).toBe(true)
})

it('should load private calendar events for authenticated user', async () => {
const request = new Request('http://localhost/')
const mockUser = { email: '[email protected]' }
mockGetUserFromHeader.mockResolvedValue(mockUser)
vi.mocked(getEvents).mockResolvedValue([])

const response = await loader({ request, params: {}, context: {} })
const data = await response.json()

expect(data.user).toEqual(mockUser)
expect(getEvents).toHaveBeenCalledWith(
expect.objectContaining({
attendeeEmail: mockUser.email,
}),
)
})
})
})
3 changes: 3 additions & 0 deletions packages/core/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('Config', () => {
NODE_ENV: 'test',
GOOGLE_AUTH_SUBJECT: '[email protected]',
CALENDAR_IDS: 'calendar1,calendar2',
PRIVATE_CALENDAR_IDS: 'calendar3,calendar4',
REMINDER_SETTINGS: JSON.stringify([
{ minutesBefore: 10, notificationType: 'email' },
{ hour: 9, minute: 0, notificationType: 'sms', target: '+1234567890' },
Expand All @@ -25,6 +26,7 @@ describe('Config', () => {
expect(parsedConfig).toEqual({
GOOGLE_AUTH_SUBJECT: '[email protected]',
CALENDAR_IDS: ['calendar1', 'calendar2'],
PRIVATE_CALENDAR_IDS: ['calendar3', 'calendar4'],
TIMEZONE: 'UTC',
REMINDER_SETTINGS: [
{ minutesBefore: 10, notificationType: 'email' },
Expand All @@ -50,6 +52,7 @@ describe('Config', () => {
expect(parsedConfig).toEqual({
GOOGLE_AUTH_SUBJECT: undefined,
CALENDAR_IDS: ['calendar1'],
PRIVATE_CALENDAR_IDS: [],
TIMEZONE: 'UTC',
REMINDER_SETTINGS: [],
REMINDER_TEMPLATE: undefined,
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export const ConfigSchema = v.object({
v.optional(v.string()),
v.transform((value) => value?.split(',') ?? []),
),
PRIVATE_CALENDAR_IDS: v.pipe(
v.optional(v.string()),
v.transform((value) => value?.split(',') ?? []),
),
TIMEZONE: v.optional(v.string(), 'UTC'),
REMINDER_SETTINGS: v.pipe(
v.optional(v.string()),
Expand Down Expand Up @@ -52,6 +56,7 @@ export function parseConfig(env: NodeJS.ProcessEnv): Config {
GOOGLE_AUTH_SUBJECT: env.GOOGLE_AUTH_SUBJECT,
CALENDAR_PROVIDER: env.CALENDAR_PROVIDER,
CALENDAR_IDS: env.CALENDAR_IDS,
PRIVATE_CALENDAR_IDS: env.PRIVATE_CALENDAR_IDS,
REMINDER_SETTINGS: env.REMINDER_SETTINGS,
REMINDER_SETTINGS_PROVIDER: env.REMINDER_SETTINGS_PROVIDER,
REMINDER_SETTINGS_FIRESTORE_DATABASE_ID: env.REMINDER_SETTINGS_FIRESTORE_DATABASE_ID,
Expand Down
Loading

0 comments on commit c5d6208

Please sign in to comment.