feat(scraper): implement One Piece data scraper for devil fruits and characters
- Added a new script to scrape devil fruits and characters from One Piece fandom. - Implemented functions to fetch, normalize, and save data in JSON, CSV, and SQL formats. - Created a structured output directory for scraped data. feat(database): update schema for devil fruits and characters - Defined new types for devil fruits and haki in the database schema. - Updated the character table to include fields for age, affiliations, devil fruit, haki, bounty, height, origin, first appearance, and picture URL. feat(ui): enhance main page and daily mode layout - Redesigned the main page with a new layout and styling for the OnePieceDle game. - Created a new daily mode page with sections for clues and user input for guesses. - Removed demo authentication routes and pages to streamline the application.
This commit is contained in:
@@ -1,11 +1,44 @@
|
||||
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { integer, sqliteTable, text, real } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
export const task = sqliteTable('task', {
|
||||
// Define haki types
|
||||
export type HakiType = 'Observation' | 'Armament' | 'Conqueror';
|
||||
|
||||
// Define devil fruit types
|
||||
export type DevilFruitType = 'Paramecia' | 'Zoan' | 'Logia' | 'Unknown';
|
||||
|
||||
// Define the devil fruit table schema
|
||||
export const devilFruit = sqliteTable('devilFruit', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull().unique(),
|
||||
type: text('type').$type<DevilFruitType>()
|
||||
});
|
||||
|
||||
// Define the character table schema
|
||||
export const character = sqliteTable('character', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
gender: text('gender'),
|
||||
age: integer('age'),
|
||||
affiliations: text('affiliations'),
|
||||
devilFruit: text('devilFruit').references(() => devilFruit.id),
|
||||
haki: text('haki', { mode: 'json' }).$type<HakiType[]>(),
|
||||
bounty: integer('bounty'),
|
||||
// height in meters as a float (e.g. 1.75)
|
||||
height: real('height'),
|
||||
origin: text('origin'),
|
||||
firstAppearance: text('firstAppearance'),
|
||||
pictureUrl: text('pictureUrl')
|
||||
});
|
||||
|
||||
// Define the caracter history table schema
|
||||
export const characterHistory = sqliteTable('characterHistory', {
|
||||
id: text('id')
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
title: text('title').notNull(),
|
||||
priority: integer('priority').notNull().default(1)
|
||||
characterId: text('characterId').references(() => character.id),
|
||||
date: integer('date'),
|
||||
createdAt: integer('createdAt').notNull().$default(() => Date.now()),
|
||||
updatedAt: integer('updatedAt').notNull().$default(() => Date.now()),
|
||||
});
|
||||
|
||||
export * from './auth.schema';
|
||||
|
||||
@@ -1,2 +1,63 @@
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||
<svelte:head>
|
||||
<title>OnePieceDle</title>
|
||||
</svelte:head>
|
||||
|
||||
<main
|
||||
class="relative min-h-screen overflow-hidden bg-slate-950 text-slate-100"
|
||||
style="background-image: url('/one-piece-bg.jpg');"
|
||||
>
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
||||
<div class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"></div>
|
||||
|
||||
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col items-center justify-between px-6 py-24 sm:py-28">
|
||||
<div class="flex w-full flex-1 flex-col items-center justify-between gap-12">
|
||||
<div class="rounded-full border border-amber-200/30 bg-amber-100/10 px-5 py-2 text-xs font-semibold uppercase tracking-[0.35em] text-amber-100">
|
||||
Jeu de devinettes Grand Line
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h1 class="text-4xl font-black uppercase tracking-[0.3em] text-amber-50 sm:text-6xl">
|
||||
OnePieceDle
|
||||
</h1>
|
||||
<p class="mt-4 max-w-2xl text-base text-slate-200 sm:text-lg">
|
||||
Devine le personnage de l'equipage, des marines ou du vaste monde. Chaque indice te rapproche du tresor.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid w-full gap-4 sm:grid-cols-2">
|
||||
<div class="rounded-2xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Voyage du jour</h2>
|
||||
<p class="mt-3 text-lg font-semibold text-white">Nouveau mystere toutes les 24 heures</p>
|
||||
<p class="mt-2 text-sm text-slate-200">Compare tes essais, debloque des indices et garde ta serie.</p>
|
||||
<a
|
||||
href="/daily"
|
||||
class="mt-5 inline-flex w-full items-center justify-center rounded-full bg-amber-300 px-5 py-3 text-sm font-semibold text-slate-900 transition hover:bg-amber-200"
|
||||
>
|
||||
Commencer
|
||||
</a>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Partie libre</h2>
|
||||
<p class="mt-3 text-lg font-semibold text-white">Entraine-toi avec des pirates legendaires</p>
|
||||
<p class="mt-2 text-sm text-slate-200">Choisis une epoque, regle la difficulte et vogue a ton rythme.</p>
|
||||
<button class="mt-5 w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50">
|
||||
Choisir un voyage
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
||||
<div class="flex flex-col items-center gap-5 text-center sm:flex-row sm:text-left">
|
||||
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
||||
Photo
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Personnage de la veille</p>
|
||||
<p class="mt-2 text-lg font-semibold text-white">Placeholder</p>
|
||||
<p class="mt-1 text-sm text-slate-200">Le revele sera visible apres la partie du jour.</p>
|
||||
</div>
|
||||
<button class="w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50 sm:w-auto">
|
||||
Voir l'archive
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
87
src/routes/daily/+page.svelte
Normal file
87
src/routes/daily/+page.svelte
Normal file
@@ -0,0 +1,87 @@
|
||||
<svelte:head>
|
||||
<title>OnePieceDle - Mode du jour</title>
|
||||
</svelte:head>
|
||||
|
||||
<main
|
||||
class="relative min-h-screen overflow-hidden bg-slate-950 text-slate-100"
|
||||
style="background-image: url('/one-piece-bg.jpg');"
|
||||
>
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
||||
<div class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"></div>
|
||||
|
||||
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col px-6 py-16 sm:py-20">
|
||||
<header class="flex flex-col items-start gap-6">
|
||||
<div class="rounded-full border border-amber-200/30 bg-amber-100/10 px-5 py-2 text-xs font-semibold uppercase tracking-[0.35em] text-amber-100">
|
||||
Mode du jour
|
||||
</div>
|
||||
<h1 class="text-3xl font-black uppercase tracking-[0.25em] text-amber-50 sm:text-5xl">
|
||||
Mystere du jour
|
||||
</h1>
|
||||
<p class="max-w-2xl text-base text-slate-200 sm:text-lg">
|
||||
Devine le personnage. Chaque indice deblocque une nouvelle piste.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section class="mt-10 grid gap-6">
|
||||
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Indices du jour</h2>
|
||||
<div class="mt-4 grid gap-3 sm:grid-cols-3">
|
||||
<div class="rounded-2xl border border-white/10 bg-slate-950/60 px-3 py-3">
|
||||
<p class="text-xs uppercase tracking-[0.25em] text-amber-100">Indice 1</p>
|
||||
<p class="mt-2 text-sm text-slate-200">Origine: ???</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-white/10 bg-slate-950/60 px-3 py-3">
|
||||
<p class="text-xs uppercase tracking-[0.25em] text-amber-100">Indice 2</p>
|
||||
<p class="mt-2 text-sm text-slate-200">Fruit du demon: ???</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-white/10 bg-slate-950/60 px-3 py-3">
|
||||
<p class="text-xs uppercase tracking-[0.25em] text-amber-100">Indice 3</p>
|
||||
<p class="mt-2 text-sm text-slate-200">Affiliation: ???</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Entrer une supposition</h2>
|
||||
<div class="mt-4 flex flex-col gap-3 sm:flex-row">
|
||||
<input
|
||||
class="w-full rounded-full border border-amber-200/30 bg-slate-900/60 px-5 py-3 text-sm text-slate-100 placeholder:text-slate-400 focus:border-amber-200/70 focus:outline-none"
|
||||
placeholder="Nom du personnage"
|
||||
type="text"
|
||||
/>
|
||||
<button class="rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition hover:bg-amber-200">
|
||||
Valider
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mt-8 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Historique</p>
|
||||
<p class="mt-2 text-sm text-slate-200">Aucune tentative pour le moment.</p>
|
||||
</div>
|
||||
<button class="rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50">
|
||||
Recommencer
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mt-8 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||
<div class="flex flex-col items-center gap-5 text-center sm:flex-row sm:text-left">
|
||||
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
||||
Photo
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Personnage d'hier</p>
|
||||
<p class="mt-2 text-lg font-semibold text-white">Placeholder</p>
|
||||
<p class="mt-1 text-sm text-slate-200">Revele apres validation de ta tentative.</p>
|
||||
</div>
|
||||
<button class="w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50 sm:w-auto">
|
||||
Voir l'archive
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
@@ -1,5 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
</script>
|
||||
|
||||
<a href={resolve('/demo/better-auth')}>better-auth</a>
|
||||
@@ -1,20 +0,0 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { Actions } from './$types';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { auth } from '$lib/server/auth';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (!event.locals.user) {
|
||||
return redirect(302, '/demo/better-auth/login');
|
||||
}
|
||||
return { user: event.locals.user };
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
signOut: async (event) => {
|
||||
await auth.api.signOut({
|
||||
headers: event.request.headers
|
||||
});
|
||||
return redirect(302, '/demo/better-auth/login');
|
||||
}
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import type { PageServerData } from './$types';
|
||||
|
||||
let { data }: { data: PageServerData } = $props();
|
||||
</script>
|
||||
|
||||
<h1>Hi, {data.user.name}!</h1>
|
||||
<p>Your user ID is {data.user.id}.</p>
|
||||
<form method="post" action="?/signOut" use:enhance>
|
||||
<button class="rounded-md bg-blue-600 px-4 py-2 text-white transition hover:bg-blue-700"
|
||||
>Sign out</button
|
||||
>
|
||||
</form>
|
||||
@@ -1,61 +0,0 @@
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import type { Actions } from './$types';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { auth } from '$lib/server/auth';
|
||||
import { APIError } from 'better-auth/api';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (event.locals.user) {
|
||||
return redirect(302, '/demo/better-auth');
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
signInEmail: async (event) => {
|
||||
const formData = await event.request.formData();
|
||||
const email = formData.get('email')?.toString() ?? '';
|
||||
const password = formData.get('password')?.toString() ?? '';
|
||||
|
||||
try {
|
||||
await auth.api.signInEmail({
|
||||
body: {
|
||||
email,
|
||||
password,
|
||||
callbackURL: '/auth/verification-success'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof APIError) {
|
||||
return fail(400, { message: error.message || 'Signin failed' });
|
||||
}
|
||||
return fail(500, { message: 'Unexpected error' });
|
||||
}
|
||||
|
||||
return redirect(302, '/demo/better-auth');
|
||||
},
|
||||
signUpEmail: async (event) => {
|
||||
const formData = await event.request.formData();
|
||||
const email = formData.get('email')?.toString() ?? '';
|
||||
const password = formData.get('password')?.toString() ?? '';
|
||||
const name = formData.get('name')?.toString() ?? '';
|
||||
|
||||
try {
|
||||
await auth.api.signUpEmail({
|
||||
body: {
|
||||
email,
|
||||
password,
|
||||
name,
|
||||
callbackURL: '/auth/verification-success'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof APIError) {
|
||||
return fail(400, { message: error.message || 'Registration failed' });
|
||||
}
|
||||
return fail(500, { message: 'Unexpected error' });
|
||||
}
|
||||
|
||||
return redirect(302, '/demo/better-auth');
|
||||
}
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import type { ActionData } from './$types';
|
||||
|
||||
let { form }: { form: ActionData } = $props();
|
||||
</script>
|
||||
|
||||
<h1>Login</h1>
|
||||
<form method="post" action="?/signInEmail" use:enhance>
|
||||
<label>
|
||||
Email
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
class="mt-1 rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
class="mt-1 rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Name (for registration)
|
||||
<input
|
||||
name="name"
|
||||
class="mt-1 rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||
/>
|
||||
</label>
|
||||
<button class="rounded-md bg-blue-600 px-4 py-2 text-white transition hover:bg-blue-700"
|
||||
>Login</button
|
||||
>
|
||||
<button
|
||||
formaction="?/signUpEmail"
|
||||
class="rounded-md bg-blue-600 px-4 py-2 text-white transition hover:bg-blue-700"
|
||||
>Register</button
|
||||
>
|
||||
</form>
|
||||
<p class="text-red-500">{form?.message ?? ''}</p>
|
||||
Reference in New Issue
Block a user