Skip to content

Commit f3b13e8

Browse files
authored
Merge pull request #22 from soranjiro/feat/walica
Feat/walica
2 parents e884425 + 74e9165 commit f3b13e8

13 files changed

Lines changed: 270 additions & 33 deletions

File tree

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ dev:
1515
pnpm run dev
1616

1717
migrate-local:
18-
cd apps/api && pnpm wrangler d1 execute tabitabi --local --file=../../migrations/0001_simple_schema.sql
18+
cd apps/api && pnpm wrangler d1 migrations apply tabitabi --local
1919

2020
migrate-remote:
21-
cd apps/api && pnpm wrangler d1 execute tabitabi --remote --file=../../migrations/0001_simple_schema.sql
21+
cd apps/api && pnpm wrangler d1 migrations apply tabitabi --remote
File renamed without changes.
File renamed without changes.

migrations/0003_add_secret_mode.sql renamed to apps/api/migrations/0003_add_secret_mode.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- Migration: Create itinerary_secrets table
22
-- Date: 2025-11-24
33

4-
CREATE TABLE itinerary_secrets (
4+
CREATE TABLE IF NOT EXISTS itinerary_secrets (
55
itinerary_id TEXT PRIMARY KEY,
66
enabled BOOLEAN DEFAULT FALSE,
77
offset_minutes INTEGER DEFAULT 60,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Add walica_id column to itineraries table
2+
ALTER TABLE itineraries ADD COLUMN walica_id TEXT;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- Migration: Refactor Walica to separate table
2+
-- Date: 2025-11-24
3+
4+
CREATE TABLE itinerary_walica_settings (
5+
itinerary_id TEXT PRIMARY KEY,
6+
walica_id TEXT NOT NULL,
7+
created_at TEXT NOT NULL,
8+
updated_at TEXT NOT NULL,
9+
FOREIGN KEY (itinerary_id) REFERENCES itineraries(id) ON DELETE CASCADE
10+
);
11+
12+
-- Migrate existing data
13+
INSERT INTO itinerary_walica_settings (itinerary_id, walica_id, created_at, updated_at)
14+
SELECT id, walica_id, created_at, updated_at
15+
FROM itineraries
16+
WHERE walica_id IS NOT NULL;
17+
18+
-- Drop column from itineraries
19+
ALTER TABLE itineraries DROP COLUMN walica_id;

apps/api/src/services/itinerary.service.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ export class ItineraryService {
88
async list(): Promise<Itinerary[]> {
99
const result = await this.db
1010
.prepare(`
11-
SELECT i.*, s.enabled as secret_enabled, s.offset_minutes as secret_offset
11+
SELECT i.*,
12+
s.enabled as secret_enabled, s.offset_minutes as secret_offset,
13+
w.walica_id as walica_id
1214
FROM itineraries i
1315
LEFT JOIN itinerary_secrets s ON i.id = s.itinerary_id
16+
LEFT JOIN itinerary_walica_settings w ON i.id = w.itinerary_id
1417
ORDER BY i.created_at DESC
1518
`)
1619
.all();
@@ -21,9 +24,12 @@ export class ItineraryService {
2124
async get(id: string): Promise<Itinerary | null> {
2225
const result = await this.db
2326
.prepare(`
24-
SELECT i.*, s.enabled as secret_enabled, s.offset_minutes as secret_offset
27+
SELECT i.*,
28+
s.enabled as secret_enabled, s.offset_minutes as secret_offset,
29+
w.walica_id as walica_id
2530
FROM itineraries i
2631
LEFT JOIN itinerary_secrets s ON i.id = s.itinerary_id
32+
LEFT JOIN itinerary_walica_settings w ON i.id = w.itinerary_id
2733
WHERE i.id = ?
2834
`)
2935
.bind(id)
@@ -41,6 +47,7 @@ export class ItineraryService {
4147
title: input.title,
4248
theme_id: input.theme_id || 'minimal',
4349
memo: input.memo ?? null,
50+
walica_id: input.walica_id ?? null,
4451
password: input.password ?? null,
4552
secret_settings: input.secret_settings ? {
4653
enabled: input.secret_settings.enabled,
@@ -70,6 +77,14 @@ export class ItineraryService {
7077
.run();
7178
}
7279

80+
// Insert into walica table if exists
81+
if (itinerary.walica_id) {
82+
await this.db
83+
.prepare('INSERT INTO itinerary_walica_settings (itinerary_id, walica_id, created_at, updated_at) VALUES (?, ?, ?, ?)')
84+
.bind(itinerary.id, itinerary.walica_id, now, now)
85+
.run();
86+
}
87+
7388
return itinerary;
7489
}
7590

@@ -116,9 +131,6 @@ export class ItineraryService {
116131
.run();
117132
} else {
118133
// Upsert settings
119-
// Check if exists first (D1 doesn't support INSERT OR REPLACE nicely with timestamps preservation if we want that, but here we just overwrite)
120-
// Actually, standard SQL UPSERT or just DELETE+INSERT or UPDATE/INSERT check.
121-
// Let's try INSERT OR REPLACE
122134
await this.db
123135
.prepare(`
124136
INSERT INTO itinerary_secrets (itinerary_id, enabled, offset_minutes, created_at, updated_at)
@@ -139,12 +151,34 @@ export class ItineraryService {
139151
}
140152
}
141153

154+
// Handle walica settings update
155+
if (input.walica_id !== undefined) {
156+
if (input.walica_id === null) {
157+
// Remove settings
158+
await this.db
159+
.prepare('DELETE FROM itinerary_walica_settings WHERE itinerary_id = ?')
160+
.bind(id)
161+
.run();
162+
} else {
163+
// Upsert settings
164+
await this.db
165+
.prepare(`
166+
INSERT INTO itinerary_walica_settings (itinerary_id, walica_id, created_at, updated_at)
167+
VALUES (?, ?, ?, ?)
168+
ON CONFLICT(itinerary_id) DO UPDATE SET
169+
walica_id = excluded.walica_id,
170+
updated_at = excluded.updated_at
171+
`)
172+
.bind(id, input.walica_id, now, now)
173+
.run();
174+
}
175+
}
176+
142177
return await this.get(id);
143178
}
144179

145180
async delete(id: string): Promise<boolean> {
146-
// Foreign key cascade should handle the secrets table, but let's be safe or rely on DB
147-
// Since we defined ON DELETE CASCADE in migration, deleting from itineraries is enough.
181+
// Foreign key cascade should handle the secrets and walica tables
148182
const result = await this.db
149183
.prepare('DELETE FROM itineraries WHERE id = ?')
150184
.bind(id)
@@ -159,6 +193,7 @@ export class ItineraryService {
159193
title: row.title,
160194
theme_id: row.theme_id,
161195
memo: row.memo,
196+
walica_id: row.walica_id,
162197
password: row.password,
163198
created_at: row.created_at,
164199
updated_at: row.updated_at,

apps/api/wrangler.toml.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ enabled = true
99
binding = "DB"
1010
database_name = "tabitabi"
1111
database_id = "PLACEHOLDER_DATABASE_ID"
12-
migrations_dir = "../../migrations"
12+
migrations_dir = "./migrations"
1313

1414
[vars]
1515
ALLOWED_ORIGINS = "*"
@@ -27,4 +27,4 @@ enabled = true
2727
binding = "DB"
2828
database_name = "tabitabi"
2929
database_id = "PLACEHOLDER_DATABASE_ID"
30-
migrations_dir = "../../migrations"
30+
migrations_dir = "./migrations"

apps/web/src/lib/themes/standard-autumn/ItineraryView.svelte

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
title?: string;
1717
theme_id?: string;
1818
memo?: string;
19+
walica_id?: string | null;
1920
secret_settings?: {
2021
enabled: boolean;
2122
offset_minutes: number;
@@ -73,6 +74,11 @@
7374
itinerary.secret_settings?.offset_minutes ?? 60,
7475
);
7576
77+
let walicaUrl = $state(
78+
itinerary.walica_id ? `https://walica.jp/group/${itinerary.walica_id}` : "",
79+
);
80+
let showWalica = $state(false);
81+
7682
let newStep = $state({
7783
title: "",
7884
date: "",
@@ -300,6 +306,20 @@
300306
}
301307
}
302308
309+
async function handleWalicaUpdate() {
310+
// Basic validation for walica.jp domain
311+
if (walicaUrl && !walicaUrl.startsWith("https://walica.jp/group/")) {
312+
alert("WalicaのURLは https://walica.jp/group/ で始まる必要があります");
313+
return;
314+
}
315+
316+
const walicaId = walicaUrl ? walicaUrl.split("/group/")[1] : null;
317+
318+
if (onUpdateItinerary) {
319+
await onUpdateItinerary({ walica_id: walicaId });
320+
}
321+
}
322+
303323
// Configure marked options
304324
marked.setOptions({
305325
breaks: true,
@@ -519,6 +539,26 @@
519539
<span>Calendar</span>
520540
</button> -->
521541

542+
{#if itinerary.walica_id}
543+
<button
544+
class="standard-autumn-bottom-btn"
545+
title="Walica"
546+
aria-label="Walica"
547+
onclick={() => (showWalica = true)}
548+
>
549+
<svg
550+
xmlns="http://www.w3.org/2000/svg"
551+
viewBox="0 0 24 24"
552+
fill="currentColor"
553+
>
554+
<path
555+
d="M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z"
556+
/>
557+
</svg>
558+
<span>Walica</span>
559+
</button>
560+
{/if}
561+
522562
<div class="standard-autumn-btn-wrapper">
523563
<button
524564
class="standard-autumn-bottom-btn"
@@ -650,6 +690,23 @@
650690
</div>
651691
{/if}
652692
</div>
693+
694+
<div class="standard-autumn-settings-divider"></div>
695+
696+
<div class="standard-autumn-settings-group">
697+
<label class="standard-autumn-settings-label">
698+
<span class="standard-autumn-settings-label-text">
699+
Walica URL
700+
</span>
701+
<input
702+
type="text"
703+
bind:value={walicaUrl}
704+
onblur={handleWalicaUpdate}
705+
placeholder="https://walica.jp/group/..."
706+
class="standard-autumn-input standard-autumn-settings-input"
707+
/>
708+
</label>
709+
</div>
653710
</div>
654711
{/if}
655712
{#if showThemeSelect}
@@ -771,6 +828,39 @@
771828
</div>
772829
{/if}
773830

831+
{#if showWalica && itinerary.walica_id}
832+
<div class="standard-autumn-walica-overlay">
833+
<div class="standard-autumn-walica-header">
834+
<button
835+
onclick={() => (showWalica = false)}
836+
class="standard-autumn-walica-close-btn"
837+
>
838+
<svg
839+
xmlns="http://www.w3.org/2000/svg"
840+
viewBox="0 0 24 24"
841+
fill="none"
842+
stroke="currentColor"
843+
stroke-width="2"
844+
stroke-linecap="round"
845+
stroke-linejoin="round"
846+
width="24"
847+
height="24"
848+
>
849+
<line x1="18" y1="6" x2="6" y2="18"></line>
850+
<line x1="6" y1="6" x2="18" y2="18"></line>
851+
</svg>
852+
閉じる
853+
</button>
854+
<span class="standard-autumn-walica-title">Walica</span>
855+
</div>
856+
<iframe
857+
src={`https://walica.jp/group/${itinerary.walica_id}`}
858+
title="Walica"
859+
class="standard-autumn-walica-frame"
860+
></iframe>
861+
</div>
862+
{/if}
863+
774864
{#if showShareDialog}
775865
<!-- svelte-ignore a11y_click_events_have_key_events -->
776866
<!-- svelte-ignore a11y_no_static_element_interactions -->

apps/web/src/lib/themes/standard-autumn/theme.css

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,3 +1303,65 @@ body {
13031303
font-size: 1.5rem;
13041304
}
13051305
}
1306+
1307+
/* Walica Overlay */
1308+
.standard-autumn-walica-overlay {
1309+
position: fixed;
1310+
top: 0;
1311+
left: 0;
1312+
right: 0;
1313+
bottom: 0;
1314+
background: #fff;
1315+
z-index: 2000;
1316+
display: flex;
1317+
flex-direction: column;
1318+
animation: slideUp 0.3s ease-out;
1319+
}
1320+
1321+
@keyframes slideUp {
1322+
from { transform: translateY(100%); }
1323+
to { transform: translateY(0); }
1324+
}
1325+
1326+
.standard-autumn-walica-header {
1327+
display: flex;
1328+
align-items: center;
1329+
justify-content: space-between;
1330+
padding: 1rem;
1331+
background: var(--standard-autumn-bg);
1332+
border-bottom: 1px solid var(--standard-autumn-border);
1333+
box-shadow: var(--standard-autumn-shadow-sm);
1334+
}
1335+
1336+
.standard-autumn-walica-close-btn {
1337+
display: flex;
1338+
align-items: center;
1339+
gap: 0.5rem;
1340+
background: none;
1341+
border: none;
1342+
font-size: 1rem;
1343+
font-weight: 600;
1344+
color: var(--standard-autumn-primary);
1345+
cursor: pointer;
1346+
padding: 0.5rem;
1347+
border-radius: 8px;
1348+
transition: background 0.2s;
1349+
}
1350+
1351+
.standard-autumn-walica-close-btn:hover {
1352+
background: rgba(169, 53, 41, 0.1);
1353+
}
1354+
1355+
.standard-autumn-walica-title {
1356+
font-family: "Hiragino Mincho ProN", "Yu Mincho", serif;
1357+
font-weight: 600;
1358+
font-size: 1.1rem;
1359+
color: var(--standard-autumn-text);
1360+
}
1361+
1362+
.standard-autumn-walica-frame {
1363+
flex: 1;
1364+
width: 100%;
1365+
border: none;
1366+
background: #fff;
1367+
}

0 commit comments

Comments
 (0)