Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dev:
pnpm run dev

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

migrate-remote:
cd apps/api && pnpm wrangler d1 execute tabitabi --remote --file=../../migrations/0001_simple_schema.sql
cd apps/api && pnpm wrangler d1 migrations apply tabitabi --remote
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- Migration: Create itinerary_secrets table
-- Date: 2025-11-24

CREATE TABLE itinerary_secrets (
CREATE TABLE IF NOT EXISTS itinerary_secrets (
itinerary_id TEXT PRIMARY KEY,
enabled BOOLEAN DEFAULT FALSE,
offset_minutes INTEGER DEFAULT 60,
Expand Down
2 changes: 2 additions & 0 deletions apps/api/migrations/0004_add_walica_id.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Add walica_id column to itineraries table
ALTER TABLE itineraries ADD COLUMN walica_id TEXT;
19 changes: 19 additions & 0 deletions apps/api/migrations/0005_refactor_walica.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Migration: Refactor Walica to separate table
-- Date: 2025-11-24

CREATE TABLE itinerary_walica_settings (
itinerary_id TEXT PRIMARY KEY,
walica_id TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (itinerary_id) REFERENCES itineraries(id) ON DELETE CASCADE
);

-- Migrate existing data
INSERT INTO itinerary_walica_settings (itinerary_id, walica_id, created_at, updated_at)
SELECT id, walica_id, created_at, updated_at
FROM itineraries
WHERE walica_id IS NOT NULL;

-- Drop column from itineraries
ALTER TABLE itineraries DROP COLUMN walica_id;
49 changes: 42 additions & 7 deletions apps/api/src/services/itinerary.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ export class ItineraryService {
async list(): Promise<Itinerary[]> {
const result = await this.db
.prepare(`
SELECT i.*, s.enabled as secret_enabled, s.offset_minutes as secret_offset
SELECT i.*,
s.enabled as secret_enabled, s.offset_minutes as secret_offset,
w.walica_id as walica_id
FROM itineraries i
LEFT JOIN itinerary_secrets s ON i.id = s.itinerary_id
LEFT JOIN itinerary_walica_settings w ON i.id = w.itinerary_id
ORDER BY i.created_at DESC
`)
.all();
Expand All @@ -21,9 +24,12 @@ export class ItineraryService {
async get(id: string): Promise<Itinerary | null> {
const result = await this.db
.prepare(`
SELECT i.*, s.enabled as secret_enabled, s.offset_minutes as secret_offset
SELECT i.*,
s.enabled as secret_enabled, s.offset_minutes as secret_offset,
w.walica_id as walica_id
FROM itineraries i
LEFT JOIN itinerary_secrets s ON i.id = s.itinerary_id
LEFT JOIN itinerary_walica_settings w ON i.id = w.itinerary_id
WHERE i.id = ?
`)
.bind(id)
Expand All @@ -41,6 +47,7 @@ export class ItineraryService {
title: input.title,
theme_id: input.theme_id || 'minimal',
memo: input.memo ?? null,
walica_id: input.walica_id ?? null,
password: input.password ?? null,
secret_settings: input.secret_settings ? {
enabled: input.secret_settings.enabled,
Expand Down Expand Up @@ -70,6 +77,14 @@ export class ItineraryService {
.run();
}

// Insert into walica table if exists
if (itinerary.walica_id) {
await this.db
.prepare('INSERT INTO itinerary_walica_settings (itinerary_id, walica_id, created_at, updated_at) VALUES (?, ?, ?, ?)')
.bind(itinerary.id, itinerary.walica_id, now, now)
.run();
}

return itinerary;
}

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

// Handle walica settings update
if (input.walica_id !== undefined) {
if (input.walica_id === null) {
// Remove settings
await this.db
.prepare('DELETE FROM itinerary_walica_settings WHERE itinerary_id = ?')
.bind(id)
.run();
} else {
// Upsert settings
await this.db
.prepare(`
INSERT INTO itinerary_walica_settings (itinerary_id, walica_id, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(itinerary_id) DO UPDATE SET
walica_id = excluded.walica_id,
updated_at = excluded.updated_at
`)
.bind(id, input.walica_id, now, now)
.run();
}
}

return await this.get(id);
}

async delete(id: string): Promise<boolean> {
// Foreign key cascade should handle the secrets table, but let's be safe or rely on DB
// Since we defined ON DELETE CASCADE in migration, deleting from itineraries is enough.
// Foreign key cascade should handle the secrets and walica tables
const result = await this.db
.prepare('DELETE FROM itineraries WHERE id = ?')
.bind(id)
Expand All @@ -159,6 +193,7 @@ export class ItineraryService {
title: row.title,
theme_id: row.theme_id,
memo: row.memo,
walica_id: row.walica_id,
password: row.password,
created_at: row.created_at,
updated_at: row.updated_at,
Expand Down
4 changes: 2 additions & 2 deletions apps/api/wrangler.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ enabled = true
binding = "DB"
database_name = "tabitabi"
database_id = "PLACEHOLDER_DATABASE_ID"
migrations_dir = "../../migrations"
migrations_dir = "./migrations"

[vars]
ALLOWED_ORIGINS = "*"
Expand All @@ -27,4 +27,4 @@ enabled = true
binding = "DB"
database_name = "tabitabi"
database_id = "PLACEHOLDER_DATABASE_ID"
migrations_dir = "../../migrations"
migrations_dir = "./migrations"
90 changes: 90 additions & 0 deletions apps/web/src/lib/themes/standard-autumn/ItineraryView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
title?: string;
theme_id?: string;
memo?: string;
walica_id?: string | null;
secret_settings?: {
enabled: boolean;
offset_minutes: number;
Expand Down Expand Up @@ -73,6 +74,11 @@
itinerary.secret_settings?.offset_minutes ?? 60,
);

let walicaUrl = $state(
itinerary.walica_id ? `https://walica.jp/group/${itinerary.walica_id}` : "",
);
let showWalica = $state(false);

let newStep = $state({
title: "",
date: "",
Expand Down Expand Up @@ -300,6 +306,20 @@
}
}

async function handleWalicaUpdate() {
// Basic validation for walica.jp domain
if (walicaUrl && !walicaUrl.startsWith("https://walica.jp/group/")) {
alert("WalicaのURLは https://walica.jp/group/ で始まる必要があります");
return;
}

const walicaId = walicaUrl ? walicaUrl.split("/group/")[1] : null;

if (onUpdateItinerary) {
await onUpdateItinerary({ walica_id: walicaId });
}
}

// Configure marked options
marked.setOptions({
breaks: true,
Expand Down Expand Up @@ -519,6 +539,26 @@
<span>Calendar</span>
</button> -->

{#if itinerary.walica_id}
<button
class="standard-autumn-bottom-btn"
title="Walica"
aria-label="Walica"
onclick={() => (showWalica = true)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
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"
/>
</svg>
<span>Walica</span>
</button>
{/if}

<div class="standard-autumn-btn-wrapper">
<button
class="standard-autumn-bottom-btn"
Expand Down Expand Up @@ -650,6 +690,23 @@
</div>
{/if}
</div>

<div class="standard-autumn-settings-divider"></div>

<div class="standard-autumn-settings-group">
<label class="standard-autumn-settings-label">
<span class="standard-autumn-settings-label-text">
Walica URL
</span>
<input
type="text"
bind:value={walicaUrl}
onblur={handleWalicaUpdate}
placeholder="https://walica.jp/group/..."
class="standard-autumn-input standard-autumn-settings-input"
/>
</label>
</div>
</div>
{/if}
{#if showThemeSelect}
Expand Down Expand Up @@ -771,6 +828,39 @@
</div>
{/if}

{#if showWalica && itinerary.walica_id}
<div class="standard-autumn-walica-overlay">
<div class="standard-autumn-walica-header">
<button
onclick={() => (showWalica = false)}
class="standard-autumn-walica-close-btn"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
width="24"
height="24"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
閉じる
</button>
<span class="standard-autumn-walica-title">Walica</span>
</div>
<iframe
src={`https://walica.jp/group/${itinerary.walica_id}`}
title="Walica"
class="standard-autumn-walica-frame"
></iframe>
</div>
{/if}

{#if showShareDialog}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
Expand Down
62 changes: 62 additions & 0 deletions apps/web/src/lib/themes/standard-autumn/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -1303,3 +1303,65 @@ body {
font-size: 1.5rem;
}
}

/* Walica Overlay */
.standard-autumn-walica-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
z-index: 2000;
display: flex;
flex-direction: column;
animation: slideUp 0.3s ease-out;
}

@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}

.standard-autumn-walica-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
background: var(--standard-autumn-bg);
border-bottom: 1px solid var(--standard-autumn-border);
box-shadow: var(--standard-autumn-shadow-sm);
}

.standard-autumn-walica-close-btn {
display: flex;
align-items: center;
gap: 0.5rem;
background: none;
border: none;
font-size: 1rem;
font-weight: 600;
color: var(--standard-autumn-primary);
cursor: pointer;
padding: 0.5rem;
border-radius: 8px;
transition: background 0.2s;
}

.standard-autumn-walica-close-btn:hover {
background: rgba(169, 53, 41, 0.1);
}

.standard-autumn-walica-title {
font-family: "Hiragino Mincho ProN", "Yu Mincho", serif;
font-weight: 600;
font-size: 1.1rem;
color: var(--standard-autumn-text);
}

.standard-autumn-walica-frame {
flex: 1;
width: 100%;
border: none;
background: #fff;
}
Loading