Skip to content

Commit d8bf9d3

Browse files
authored
Merge pull request #33 from kc3hack/add-dashboard
Add dashboard
2 parents 124a3ed + ff6fe5c commit d8bf9d3

25 files changed

Lines changed: 2183 additions & 296 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
backend/.secrets/

backend/.dev.vars.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ BETTER_AUTH_URL=http://localhost:8787
77
GOOGLE_CLIENT_ID=your-google-client-id
88
GOOGLE_CLIENT_SECRET=your-google-client-secret
99
BETTER_AUTH_SECRET=replace-with-random-long-secret
10+
GOOGLE_MAPS_API_KEY=your-google-maps-api-key
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
BETTER_AUTH_SECRET=replace-with-random-long-secret
22
GOOGLE_CLIENT_ID=your-develop-google-client-id
33
GOOGLE_CLIENT_SECRET=your-develop-google-client-secret
4+
GOOGLE_MAPS_API_KEY=your-google-maps-api-key

backend/.secrets/main.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
BETTER_AUTH_SECRET=replace-with-random-long-secret
22
GOOGLE_CLIENT_ID=your-main-google-client-id
33
GOOGLE_CLIENT_SECRET=your-main-google-client-secret
4+
GOOGLE_MAPS_API_KEY=your-google-maps-api-key

backend/.secrets/pr.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
BETTER_AUTH_SECRET=replace-with-random-long-secret
22
GOOGLE_CLIENT_ID=your-pr-google-client-id
33
GOOGLE_CLIENT_SECRET=your-pr-google-client-secret
4+
GOOGLE_MAPS_API_KEY=your-google-maps-api-key

backend/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
## 技術スタック
77

8-
- Hono
8+
- Honogit pull
99
- Cloudflare Workers
1010
- Cloudflare Workflows
1111
- Workers AI
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
create table "morning_briefing_cache" (
2+
"id" text not null primary key,
3+
"user_id" text not null,
4+
"slot_key" text not null,
5+
"location_key" text not null,
6+
"prep_minutes" integer not null,
7+
"payload_json" text not null,
8+
"created_at" date not null,
9+
"updated_at" date not null
10+
);
11+
12+
create unique index "morning_briefing_cache_unique_idx"
13+
on "morning_briefing_cache" ("user_id", "slot_key", "location_key", "prep_minutes");
14+
15+
create index "morning_briefing_cache_user_slot_idx"
16+
on "morning_briefing_cache" ("user_id", "slot_key");

backend/src/app.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ import type { Context } from "hono";
22
import { Hono } from "hono";
33
import { getAllowedOrigins, isAllowedOrigin } from "./lib/origins";
44
import { registerAuthRoutes } from "./routes/auth-routes";
5+
import { registerBriefingRoutes } from "./routes/briefing-routes";
6+
import { registerCalendarRoutes } from "./routes/calendar-routes";
57
import { registerRootRoutes } from "./routes/root-routes";
68
import { registerTaskRoutes } from "./routes/task-routes";
9+
import { registerTransitRoutes } from "./routes/transit-routes";
710
import { registerWorkflowRoutes } from "./routes/workflow-routes";
811
import type { App } from "./types/app";
912

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

5053
registerRootRoutes(app);
5154
registerAuthRoutes(app);
55+
registerBriefingRoutes(app);
56+
registerCalendarRoutes(app);
5257
registerTaskRoutes(app);
58+
registerTransitRoutes(app);
5359
registerWorkflowRoutes(app);
5460

5561
return app;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { createAuth } from "../../lib/auth";
2+
import type { CalendarEvent, TodayEventsResult } from "./google-calendar.types";
3+
4+
// ---------------------------------------------------------------------------
5+
// Token helpers
6+
// ---------------------------------------------------------------------------
7+
8+
const GOOGLE_PROVIDER_ID = "google";
9+
10+
/**
11+
* Retrieve a valid Google access token for the given user.
12+
*
13+
* Better Auth handles token decryption / refresh internally. We should always
14+
* use the public `getAccessToken` API instead of reading `account` rows
15+
* directly.
16+
*/
17+
async function getGoogleAccessToken(
18+
env: Env,
19+
userId: string,
20+
): Promise<string | null> {
21+
const auth = createAuth(env);
22+
try {
23+
const tokenPayload = await auth.api.getAccessToken({
24+
body: {
25+
providerId: GOOGLE_PROVIDER_ID,
26+
userId,
27+
},
28+
});
29+
30+
if (
31+
!tokenPayload ||
32+
typeof tokenPayload.accessToken !== "string" ||
33+
tokenPayload.accessToken.trim().length === 0
34+
) {
35+
return null;
36+
}
37+
38+
return tokenPayload.accessToken;
39+
} catch (error) {
40+
console.error("Failed to get Google access token:", error);
41+
return null;
42+
}
43+
}
44+
45+
// ---------------------------------------------------------------------------
46+
// Calendar API
47+
// ---------------------------------------------------------------------------
48+
49+
/**
50+
* Fetch today's events from the authenticated user's primary Google Calendar.
51+
*
52+
* Time zone is fixed to `Asia/Tokyo` (JST).
53+
*/
54+
export async function getTodayEvents(
55+
env: Env,
56+
userId: string,
57+
): Promise<TodayEventsResult> {
58+
// Compute "today" in JST
59+
const jstNow = new Date(Date.now() + 9 * 60 * 60 * 1000);
60+
const date = jstNow.toISOString().split("T")[0] as string;
61+
62+
const accessToken = await getGoogleAccessToken(env, userId);
63+
if (!accessToken) {
64+
return { date, events: [], earliestEvent: null };
65+
}
66+
67+
const timeMin = new Date(`${date}T00:00:00+09:00`).toISOString();
68+
const timeMax = new Date(`${date}T23:59:59+09:00`).toISOString();
69+
70+
const params = new URLSearchParams({
71+
timeMin,
72+
timeMax,
73+
singleEvents: "true",
74+
orderBy: "startTime",
75+
timeZone: "Asia/Tokyo",
76+
});
77+
78+
const res = await fetch(
79+
`https://www.googleapis.com/calendar/v3/calendars/primary/events?${params}`,
80+
{ headers: { Authorization: `Bearer ${accessToken}` } },
81+
);
82+
83+
if (!res.ok) {
84+
console.error("Google Calendar API error:", res.status, await res.text());
85+
return { date, events: [], earliestEvent: null };
86+
}
87+
88+
// biome-ignore lint/suspicious/noExplicitAny: Google Calendar API response
89+
const data = (await res.json()) as any;
90+
91+
const events: CalendarEvent[] = (data.items ?? [])
92+
// biome-ignore lint/suspicious/noExplicitAny: Google Calendar event
93+
.filter((item: any) => item.status !== "cancelled")
94+
// biome-ignore lint/suspicious/noExplicitAny: Google Calendar event
95+
.map((item: any) => ({
96+
id: item.id as string,
97+
summary: (item.summary as string) ?? "(無題)",
98+
location: (item.location as string) ?? null,
99+
start: item.start?.dateTime ?? item.start?.date ?? "",
100+
end: item.end?.dateTime ?? item.end?.date ?? "",
101+
isAllDay: !item.start?.dateTime,
102+
}));
103+
104+
const timedEvents = events.filter((e) => !e.isAllDay);
105+
const earliestEvent =
106+
timedEvents.length > 0 ? (timedEvents[0] ?? null) : null;
107+
108+
return { date, events, earliestEvent };
109+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/** A single Google Calendar event (simplified). */
2+
export type CalendarEvent = {
3+
id: string;
4+
summary: string;
5+
location: string | null;
6+
/** ISO-8601 datetime or date string. */
7+
start: string;
8+
/** ISO-8601 datetime or date string. */
9+
end: string;
10+
/** true for all-day events. */
11+
isAllDay: boolean;
12+
};
13+
14+
/** Result of fetching today's events. */
15+
export type TodayEventsResult = {
16+
/** YYYY-MM-DD */
17+
date: string;
18+
events: CalendarEvent[];
19+
/** The earliest *timed* (non-all-day) event, or null. */
20+
earliestEvent: CalendarEvent | null;
21+
};

0 commit comments

Comments
 (0)