Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e35ff8f
feat: add morning briefing and transit direction features with Google…
ikotome Feb 21, 2026
38a6abc
Merge branch 'develop' into add-dashboard
ikotome Feb 21, 2026
e1d1fc9
feat(weather): integrate weather information for morning briefing and…
ikotome Feb 21, 2026
3370d66
Merge branch 'develop' into add-dashboard
ikotome Feb 21, 2026
f7f6377
feat(transit): update to use Google Routes API and enhance transit di…
ikotome Feb 21, 2026
2a5a9b1
feat(api): add API helpers for calendar, transit, and morning briefing
ikotome Feb 21, 2026
2bd8961
refactor: improve code formatting and readability across multiple ser…
ikotome Feb 21, 2026
cb7b034
feat(env): add GOOGLE_MAPS_API_KEY to example env files and .gitignor…
ikotome Feb 21, 2026
b2eece5
refactor(google-calendar): improve formatting of earliestEvent assign…
ikotome Feb 21, 2026
fdacdea
feat(calendar): add logging for access token retrieval and API reques…
ikotome Feb 21, 2026
75b0994
refactor(google-calendar): improve logging format in getTodayEvents f…
ikotome Feb 21, 2026
9e9dccd
refactor(dashboard): simplify layout and improve widget structure
ikotome Feb 21, 2026
f570482
feat(dashboard): add links to calendar, maps, and weather in widget c…
ikotome Feb 21, 2026
ae5cbf4
refactor(dashboard): update WidgetCard to use anchor tags for links a…
ikotome Feb 21, 2026
a475351
Add dashboard calendar test panel and fix token retrieval
nenrinyear Feb 21, 2026
a8c5404
Fix frontend formatting and backend lint warning
nenrinyear Feb 21, 2026
513dd55
refactor(dashboard): replace getMorningDashboard with fetchMorningBri…
ikotome Feb 21, 2026
5b120c5
fix(docs): correct spelling of Hono in README.md
ikotome Feb 21, 2026
e547b98
refactor(dashboard): remove unused calendar-related code and clean up…
ikotome Feb 21, 2026
76bfd3b
feat(dashboard): implement caching for morning briefing and add force…
ikotome Feb 21, 2026
0d8cb1b
refactor(dashboard): remove refreshToken state and update dependencie…
ikotome Feb 22, 2026
3bbbb7d
feat(dashboard): enhance error handling with specific messages and lo…
ikotome Feb 22, 2026
b775b16
fix(dashboard): improve error handling for unauthorized access
ikotome Feb 22, 2026
c259394
move root page to task-decomp route
nenrinyear Feb 21, 2026
8649667
split task-decomp page into helpers and step components
nenrinyear Feb 21, 2026
d2aef94
add root landing page and apply biome formatting
nenrinyear Feb 21, 2026
fee0ca6
remove duplicated route buttons from landing
nenrinyear Feb 21, 2026
8776cba
simplify task-decomp mobile UI and remove redundant cards
nenrinyear Feb 21, 2026
0be41ca
show steps before authentication on task-decomp
nenrinyear Feb 21, 2026
f07d25f
add header history drawer for task-decomp workflow
nenrinyear Feb 21, 2026
7f0f295
move history entry point to account action row
nenrinyear Feb 21, 2026
259fd3c
fix history drawer footer close control overlap
nenrinyear Feb 21, 2026
116df6c
refine task-decomp status UI and use summary title for calendar events
nenrinyear Feb 21, 2026
a45203f
adjust compose guidance wording for already-authorized users
nenrinyear Feb 21, 2026
07d504c
add navigation links from task-decomp to top and dashboard
nenrinyear Feb 21, 2026
95ad548
add account-scoped draft autosave for task-decomp input
nenrinyear Feb 21, 2026
4765069
improve task input readability with themed field backgrounds
nenrinyear Feb 21, 2026
8f0126d
unify timezone handling across task-decomp flow
nenrinyear Feb 21, 2026
ff6fe5c
address PR feedback on task-decomp review comments
nenrinyear Feb 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
backend/.secrets/
1 change: 1 addition & 0 deletions backend/.dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ BETTER_AUTH_URL=http://localhost:8787
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
BETTER_AUTH_SECRET=replace-with-random-long-secret
GOOGLE_MAPS_API_KEY=your-google-maps-api-key
1 change: 1 addition & 0 deletions backend/.secrets/develop.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
BETTER_AUTH_SECRET=replace-with-random-long-secret
GOOGLE_CLIENT_ID=your-develop-google-client-id
GOOGLE_CLIENT_SECRET=your-develop-google-client-secret
GOOGLE_MAPS_API_KEY=your-google-maps-api-key
1 change: 1 addition & 0 deletions backend/.secrets/main.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
BETTER_AUTH_SECRET=replace-with-random-long-secret
GOOGLE_CLIENT_ID=your-main-google-client-id
GOOGLE_CLIENT_SECRET=your-main-google-client-secret
GOOGLE_MAPS_API_KEY=your-google-maps-api-key
1 change: 1 addition & 0 deletions backend/.secrets/pr.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
BETTER_AUTH_SECRET=replace-with-random-long-secret
GOOGLE_CLIENT_ID=your-pr-google-client-id
GOOGLE_CLIENT_SECRET=your-pr-google-client-secret
GOOGLE_MAPS_API_KEY=your-google-maps-api-key
2 changes: 1 addition & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## 技術スタック

- Hono
- Honogit pull
- Cloudflare Workers
- Cloudflare Workflows
- Workers AI
Expand Down
16 changes: 16 additions & 0 deletions backend/migrations/0003_morning_briefing_cache.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
create table "morning_briefing_cache" (
"id" text not null primary key,
"user_id" text not null,
"slot_key" text not null,
"location_key" text not null,
"prep_minutes" integer not null,
"payload_json" text not null,
"created_at" date not null,
"updated_at" date not null
);

create unique index "morning_briefing_cache_unique_idx"
on "morning_briefing_cache" ("user_id", "slot_key", "location_key", "prep_minutes");

create index "morning_briefing_cache_user_slot_idx"
on "morning_briefing_cache" ("user_id", "slot_key");
6 changes: 6 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import type { Context } from "hono";
import { Hono } from "hono";
import { getAllowedOrigins, isAllowedOrigin } from "./lib/origins";
import { registerAuthRoutes } from "./routes/auth-routes";
import { registerBriefingRoutes } from "./routes/briefing-routes";
import { registerCalendarRoutes } from "./routes/calendar-routes";
import { registerRootRoutes } from "./routes/root-routes";
import { registerTaskRoutes } from "./routes/task-routes";
import { registerTransitRoutes } from "./routes/transit-routes";
import { registerWorkflowRoutes } from "./routes/workflow-routes";
import type { App } from "./types/app";

Expand Down Expand Up @@ -49,7 +52,10 @@ export function createApp(): App {

registerRootRoutes(app);
registerAuthRoutes(app);
registerBriefingRoutes(app);
registerCalendarRoutes(app);
registerTaskRoutes(app);
registerTransitRoutes(app);
registerWorkflowRoutes(app);

return app;
Expand Down
109 changes: 109 additions & 0 deletions backend/src/features/google-calendar/google-calendar.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { createAuth } from "../../lib/auth";
import type { CalendarEvent, TodayEventsResult } from "./google-calendar.types";

// ---------------------------------------------------------------------------
// Token helpers
// ---------------------------------------------------------------------------

const GOOGLE_PROVIDER_ID = "google";

/**
* Retrieve a valid Google access token for the given user.
*
* Better Auth handles token decryption / refresh internally. We should always
* use the public `getAccessToken` API instead of reading `account` rows
* directly.
*/
async function getGoogleAccessToken(
env: Env,
userId: string,
): Promise<string | null> {
const auth = createAuth(env);
try {
const tokenPayload = await auth.api.getAccessToken({
body: {
providerId: GOOGLE_PROVIDER_ID,
userId,
},
});

if (
!tokenPayload ||
typeof tokenPayload.accessToken !== "string" ||
tokenPayload.accessToken.trim().length === 0
) {
return null;
}

return tokenPayload.accessToken;
} catch (error) {
console.error("Failed to get Google access token:", error);
return null;
}
}

// ---------------------------------------------------------------------------
// Calendar API
// ---------------------------------------------------------------------------

/**
* Fetch today's events from the authenticated user's primary Google Calendar.
*
* Time zone is fixed to `Asia/Tokyo` (JST).
*/
export async function getTodayEvents(
env: Env,
userId: string,
): Promise<TodayEventsResult> {
// Compute "today" in JST
const jstNow = new Date(Date.now() + 9 * 60 * 60 * 1000);
const date = jstNow.toISOString().split("T")[0] as string;

const accessToken = await getGoogleAccessToken(env, userId);
if (!accessToken) {
return { date, events: [], earliestEvent: null };
}

const timeMin = new Date(`${date}T00:00:00+09:00`).toISOString();
const timeMax = new Date(`${date}T23:59:59+09:00`).toISOString();

const params = new URLSearchParams({
timeMin,
timeMax,
singleEvents: "true",
orderBy: "startTime",
timeZone: "Asia/Tokyo",
});

const res = await fetch(
`https://www.googleapis.com/calendar/v3/calendars/primary/events?${params}`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);

if (!res.ok) {
console.error("Google Calendar API error:", res.status, await res.text());
return { date, events: [], earliestEvent: null };
}

// biome-ignore lint/suspicious/noExplicitAny: Google Calendar API response
const data = (await res.json()) as any;

const events: CalendarEvent[] = (data.items ?? [])
// biome-ignore lint/suspicious/noExplicitAny: Google Calendar event
.filter((item: any) => item.status !== "cancelled")
// biome-ignore lint/suspicious/noExplicitAny: Google Calendar event
.map((item: any) => ({
id: item.id as string,
summary: (item.summary as string) ?? "(無題)",
location: (item.location as string) ?? null,
start: item.start?.dateTime ?? item.start?.date ?? "",
end: item.end?.dateTime ?? item.end?.date ?? "",
isAllDay: !item.start?.dateTime,
}));

const timedEvents = events.filter((e) => !e.isAllDay);
const earliestEvent =
timedEvents.length > 0 ? (timedEvents[0] ?? null) : null;

return { date, events, earliestEvent };
}
21 changes: 21 additions & 0 deletions backend/src/features/google-calendar/google-calendar.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/** A single Google Calendar event (simplified). */
export type CalendarEvent = {
id: string;
summary: string;
location: string | null;
/** ISO-8601 datetime or date string. */
start: string;
/** ISO-8601 datetime or date string. */
end: string;
/** true for all-day events. */
isAllDay: boolean;
};

/** Result of fetching today's events. */
export type TodayEventsResult = {
/** YYYY-MM-DD */
date: string;
events: CalendarEvent[];
/** The earliest *timed* (non-all-day) event, or null. */
earliestEvent: CalendarEvent | null;
};
Loading