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:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,3 +23,6 @@ vite.config.js.timestamp-*
|
|||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
# SQLite
|
# SQLite
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
|
# Script outputs
|
||||||
|
/scraped-data
|
||||||
130
SCRAPER.md
Normal file
130
SCRAPER.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# One Piece Scraper
|
||||||
|
|
||||||
|
Script pour scraper les données des personnages de One Piece depuis le fandom wiki français.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Installe les dépendances d'abord :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Scraper tous les formats (JSON, CSV, SQL)
|
||||||
|
npm run scrape
|
||||||
|
|
||||||
|
# Ou spécifier un format
|
||||||
|
node scripts/scrape-onepiece.js json # JSON uniquement
|
||||||
|
node scripts/scrape-onepiece.js csv # CSV uniquement
|
||||||
|
node scripts/scrape-onepiece.js sql # SQL uniquement
|
||||||
|
node scripts/scrape-onepiece.js all # Tous les formats (défaut)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sortie
|
||||||
|
|
||||||
|
Les données seront sauvegardées dans le dossier `scraped-data/` :
|
||||||
|
|
||||||
|
- **characters.json** - Format JSON avec toutes les données structurées
|
||||||
|
- **characters.csv** - Format CSV pour importer dans Excel/Sheets
|
||||||
|
- **characters.sql** - Statements SQL avec gestion des conflits (upsert)
|
||||||
|
|
||||||
|
## Données extraites
|
||||||
|
|
||||||
|
Pour chaque personnage :
|
||||||
|
- 📝 **Nom** - Nom du personnage
|
||||||
|
- 👤 **Genre** - Masculin/Féminin (extrait des catégories)
|
||||||
|
- 🎂 **Âge** - Âge le plus récent (post-ellipse), chiffres uniquement
|
||||||
|
- 📏 **Taille** - Normalisée en mètres (format: "2.74" ou "174" pour cm)
|
||||||
|
- 🌍 **Origine** - Lieu d'origine (sans parenthèses)
|
||||||
|
- 😈 **Fruit du Démon** - Nom du fruit (si applicable)
|
||||||
|
- 👥 **Affiliations** - Liste des affiliations (équipages, organisations)
|
||||||
|
- 💰 **Prime** - Bounty la plus récente
|
||||||
|
- ⚡ **Haki** - Liste des types de Haki (Observation, Armament, Conqueror)
|
||||||
|
- 📖 **Première Apparition** - Numéro de chapitre
|
||||||
|
- 🖼️ **Image** - URL de l'image portrait nettoyée
|
||||||
|
- 🔗 **Fandom URL** - Lien vers la page wiki
|
||||||
|
|
||||||
|
## Personnages scrapés
|
||||||
|
|
||||||
|
Le script scrape tous les personnages canon de la liste officielle du Fandom wiki français.
|
||||||
|
|
||||||
|
**Personnages actuellement filtrés** : Luffy et Moria (modifiable dans `fetchAllCharactersUrl`)
|
||||||
|
|
||||||
|
Pour scraper tous les personnages, retire le filtre dans la fonction `fetchAllCharactersUrl`.
|
||||||
|
|
||||||
|
## Fonctionnalités avancées
|
||||||
|
|
||||||
|
### Requêtes parallèles
|
||||||
|
- Le scraper traite 5 personnages en parallèle pour plus d'efficacité
|
||||||
|
- Concurrency configurable dans le code
|
||||||
|
|
||||||
|
### Nettoyage des données
|
||||||
|
- Suppression automatique des citations/références (`<sup>` tags)
|
||||||
|
- Âge : extraction du dernier âge (après ellipse), sans parenthèses
|
||||||
|
- Taille : normalisation m/cm et suppression des parenthèses
|
||||||
|
- Origine : suppression du contenu entre parenthèses
|
||||||
|
- Image : sélection automatique du portrait
|
||||||
|
- Première apparition : extraction du numéro de chapitre uniquement
|
||||||
|
|
||||||
|
### SQL Upsert
|
||||||
|
- Le SQL généré utilise `INSERT ... ON CONFLICT(name) DO UPDATE`
|
||||||
|
- Met à jour les personnages existants au lieu de créer des doublons
|
||||||
|
- Compatible SQLite (utilisé par le projet)
|
||||||
|
|
||||||
|
### Formats de données
|
||||||
|
- **Haki** : Stocké comme array JSON dans SQL : `["Observation","Armament"]`
|
||||||
|
- **Affiliations** : Liste dans JSON, comma-separated dans CSV/SQL
|
||||||
|
- Tous les champs nullable supportent `NULL` dans SQL
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Modifier les personnages filtrés
|
||||||
|
|
||||||
|
Dans `scripts/scrape-onepiece.js`, fonction `fetchAllCharactersUrl` :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Filtrer pour des personnages spécifiques
|
||||||
|
if (nameLower.includes('luffy') || nameLower.includes('moria')) {
|
||||||
|
characters.push({ name: charName, url: charLink });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ajuster la concurrence
|
||||||
|
|
||||||
|
Dans la fonction `main` :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const concurrency = 5; // Nombre de requêtes simultanées
|
||||||
|
```
|
||||||
|
|
||||||
|
## Importer les données SQL
|
||||||
|
|
||||||
|
Après avoir généré le fichier SQL, importe-le dans la base de données :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:import
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce script :
|
||||||
|
- Lit automatiquement `scraped-data/characters.sql`
|
||||||
|
- Exécute chaque statement individuellement
|
||||||
|
- Affiche une barre de progression
|
||||||
|
- Gère les erreurs sans bloquer l'import complet
|
||||||
|
- Utilise le upsert pour éviter les doublons
|
||||||
|
|
||||||
|
**Note** : Assure-toi d'avoir exécuté les migrations avant l'import :
|
||||||
|
```bash
|
||||||
|
npm run db:migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes techniques
|
||||||
|
|
||||||
|
- Source : `https://onepiece.fandom.com/fr/wiki`
|
||||||
|
- Parseur : Cheerio (DOM parsing)
|
||||||
|
- Traitement parallèle avec `Promise.all`
|
||||||
|
- User-Agent configuré pour éviter les blocages
|
||||||
|
- Pas de délai entre requêtes (batches parallèles)
|
||||||
|
- Gestion d'erreurs par personnage (ne bloque pas le scraping complet)
|
||||||
85
drizzle/0000_dapper_sage.sql
Normal file
85
drizzle/0000_dapper_sage.sql
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
CREATE TABLE `character` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`gender` text,
|
||||||
|
`age` integer,
|
||||||
|
`affiliations` text,
|
||||||
|
`devilFruit` text,
|
||||||
|
`haki` text,
|
||||||
|
`bounty` integer,
|
||||||
|
`height` real,
|
||||||
|
`origin` text,
|
||||||
|
`firstAppearance` text,
|
||||||
|
`pictureUrl` text,
|
||||||
|
FOREIGN KEY (`devilFruit`) REFERENCES `devilFruit`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `characterHistory` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`characterId` text,
|
||||||
|
`date` integer,
|
||||||
|
`createdAt` integer NOT NULL,
|
||||||
|
`updatedAt` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`characterId`) REFERENCES `character`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `devilFruit` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`type` text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `devilFruit_name_unique` ON `devilFruit` (`name`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `account` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`account_id` text NOT NULL,
|
||||||
|
`provider_id` text NOT NULL,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`access_token` text,
|
||||||
|
`refresh_token` text,
|
||||||
|
`id_token` text,
|
||||||
|
`access_token_expires_at` integer,
|
||||||
|
`refresh_token_expires_at` integer,
|
||||||
|
`scope` text,
|
||||||
|
`password` text,
|
||||||
|
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||||
|
`updated_at` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `account_userId_idx` ON `account` (`user_id`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `session` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`expires_at` integer NOT NULL,
|
||||||
|
`token` text NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||||
|
`updated_at` integer NOT NULL,
|
||||||
|
`ip_address` text,
|
||||||
|
`user_agent` text,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `session_userId_idx` ON `session` (`user_id`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `user` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`email` text NOT NULL,
|
||||||
|
`email_verified` integer DEFAULT false NOT NULL,
|
||||||
|
`image` text,
|
||||||
|
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `verification` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`identifier` text NOT NULL,
|
||||||
|
`value` text NOT NULL,
|
||||||
|
`expires_at` integer NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `verification_identifier_idx` ON `verification` (`identifier`);
|
||||||
576
drizzle/meta/0000_snapshot.json
Normal file
576
drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,576 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "40edd98b-5a47-4a5e-a5b8-8a0f6eaaec76",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"tables": {
|
||||||
|
"character": {
|
||||||
|
"name": "character",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"name": "age",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"affiliations": {
|
||||||
|
"name": "affiliations",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"devilFruit": {
|
||||||
|
"name": "devilFruit",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki": {
|
||||||
|
"name": "haki",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"bounty": {
|
||||||
|
"name": "bounty",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"name": "height",
|
||||||
|
"type": "real",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"name": "origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"firstAppearance": {
|
||||||
|
"name": "firstAppearance",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"pictureUrl": {
|
||||||
|
"name": "pictureUrl",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"character_devilFruit_devilFruit_id_fk": {
|
||||||
|
"name": "character_devilFruit_devilFruit_id_fk",
|
||||||
|
"tableFrom": "character",
|
||||||
|
"tableTo": "devilFruit",
|
||||||
|
"columnsFrom": [
|
||||||
|
"devilFruit"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"characterHistory": {
|
||||||
|
"name": "characterHistory",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"characterId": {
|
||||||
|
"name": "characterId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"name": "date",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"characterHistory_characterId_character_id_fk": {
|
||||||
|
"name": "characterHistory_characterId_character_id_fk",
|
||||||
|
"tableFrom": "characterHistory",
|
||||||
|
"tableTo": "character",
|
||||||
|
"columnsFrom": [
|
||||||
|
"characterId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"devilFruit": {
|
||||||
|
"name": "devilFruit",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"devilFruit_name_unique": {
|
||||||
|
"name": "devilFruit_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"name": "account",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"name": "account_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"provider_id": {
|
||||||
|
"name": "provider_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"access_token": {
|
||||||
|
"name": "access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"name": "refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"id_token": {
|
||||||
|
"name": "id_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"access_token_expires_at": {
|
||||||
|
"name": "access_token_expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"refresh_token_expires_at": {
|
||||||
|
"name": "refresh_token_expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"account_userId_idx": {
|
||||||
|
"name": "account_userId_idx",
|
||||||
|
"columns": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_user_id_user_id_fk": {
|
||||||
|
"name": "account_user_id_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"name": "session",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"ip_address": {
|
||||||
|
"name": "ip_address",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_agent": {
|
||||||
|
"name": "user_agent",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"session_token_unique": {
|
||||||
|
"name": "session_token_unique",
|
||||||
|
"columns": [
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"session_userId_idx": {
|
||||||
|
"name": "session_userId_idx",
|
||||||
|
"columns": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_user_id_user_id_fk": {
|
||||||
|
"name": "session_user_id_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email_verified": {
|
||||||
|
"name": "email_verified",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_email_unique": {
|
||||||
|
"name": "user_email_unique",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"name": "verification",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"verification_identifier_idx": {
|
||||||
|
"name": "verification_identifier_idx",
|
||||||
|
"columns": [
|
||||||
|
"identifier"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1772148571269,
|
||||||
|
"tag": "0000_dapper_sage",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
648
package-lock.json
generated
648
package-lock.json
generated
@@ -18,7 +18,10 @@
|
|||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@types/node": "^24",
|
"@types/node": "^24",
|
||||||
|
"axios": "^1.6.0",
|
||||||
"better-auth": "^1.4.18",
|
"better-auth": "^1.4.18",
|
||||||
|
"cheerio": "^1.0.0-rc.12",
|
||||||
|
"csv-writer": "^1.6.0",
|
||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.8",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
@@ -2695,6 +2698,25 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.13.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||||
|
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.11",
|
||||||
|
"form-data": "^4.0.5",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
@@ -2841,6 +2863,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
@@ -2859,6 +2888,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
@@ -2886,6 +2929,50 @@
|
|||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cheerio": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cheerio-select": "^2.1.0",
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.2.2",
|
||||||
|
"encoding-sniffer": "^0.2.1",
|
||||||
|
"htmlparser2": "^10.1.0",
|
||||||
|
"parse5": "^7.3.0",
|
||||||
|
"parse5-htmlparser2-tree-adapter": "^7.1.0",
|
||||||
|
"parse5-parser-stream": "^7.1.2",
|
||||||
|
"undici": "^7.19.0",
|
||||||
|
"whatwg-mimetype": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.18.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cheerio-select": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
@@ -2932,6 +3019,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -2995,6 +3095,36 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@@ -3008,6 +3138,13 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/csv-writer": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/data-uri-to-buffer": {
|
"node_modules/data-uri-to-buffer": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||||
@@ -3060,6 +3197,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
|
||||||
@@ -3077,6 +3224,65 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/drizzle-kit": {
|
"node_modules/drizzle-kit": {
|
||||||
"version": "0.31.9",
|
"version": "0.31.9",
|
||||||
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.9.tgz",
|
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.9.tgz",
|
||||||
@@ -3219,6 +3425,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/encoding-sniffer": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"whatwg-encoding": "^3.1.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.19.0",
|
"version": "5.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
|
||||||
@@ -3233,6 +3468,68 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.12",
|
"version": "0.25.12",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||||
@@ -3662,6 +3959,44 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/formdata-polyfill": {
|
"node_modules/formdata-polyfill": {
|
||||||
"version": "4.0.10",
|
"version": "4.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||||
@@ -3690,6 +4025,55 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-symbols": "^1.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dunder-proto": "^1.0.1",
|
||||||
|
"es-object-atoms": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-tsconfig": {
|
"node_modules/get-tsconfig": {
|
||||||
"version": "4.13.6",
|
"version": "4.13.6",
|
||||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
|
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
|
||||||
@@ -3729,6 +4113,19 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
@@ -3746,6 +4143,94 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/htmlparser2": {
|
||||||
|
"version": "10.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
|
||||||
|
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.2.2",
|
||||||
|
"entities": "^7.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/htmlparser2/node_modules/entities": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -4301,6 +4786,39 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mini-svg-data-uri": {
|
"node_modules/mini-svg-data-uri": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||||
@@ -4433,6 +4951,19 @@
|
|||||||
"url": "https://opencollective.com/node-fetch"
|
"url": "https://opencollective.com/node-fetch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/obug": {
|
"node_modules/obug": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
|
||||||
@@ -4507,6 +5038,59 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"entities": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5-parser-stream": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5/node_modules/entities": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
@@ -4807,6 +5391,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -4916,6 +5507,13 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
@@ -5247,6 +5845,16 @@
|
|||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici": {
|
||||||
|
"version": "7.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
|
||||||
|
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
@@ -5867,6 +6475,30 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/whatwg-encoding": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||||
|
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-mimetype": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/whatwg-url": {
|
"node_modules/whatwg-url": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
@@ -5926,22 +6558,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
|
||||||
"version": "2.8.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
|
||||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
|
||||||
"extraneous": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"yaml": "bin.mjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/eemeli"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|||||||
@@ -16,7 +16,9 @@
|
|||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:studio": "drizzle-kit studio",
|
"db:studio": "drizzle-kit studio",
|
||||||
"auth:schema": "npx @better-auth/cli generate --config src/lib/server/auth.ts --output src/lib/server/db/auth.schema.ts --yes"
|
"db:import": "node scripts/import-sql.js",
|
||||||
|
"auth:schema": "npx @better-auth/cli generate --config src/lib/server/auth.ts --output src/lib/server/db/auth.schema.ts --yes",
|
||||||
|
"scrape": "node scripts/scrape-onepiece.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^2.0.2",
|
"@eslint/compat": "^2.0.2",
|
||||||
@@ -30,6 +32,8 @@
|
|||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@types/node": "^24",
|
"@types/node": "^24",
|
||||||
"better-auth": "^1.4.18",
|
"better-auth": "^1.4.18",
|
||||||
|
"cheerio": "^1.0.0-rc.12",
|
||||||
|
"csv-writer": "^1.6.0",
|
||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.8",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
|
|||||||
104
scripts/import-sql.js
Normal file
104
scripts/import-sql.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { createClient } from '@libsql/client';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
const DATABASE_URL = process.env.DATABASE_URL || 'file:local.db';
|
||||||
|
|
||||||
|
const client = createClient({
|
||||||
|
url: DATABASE_URL
|
||||||
|
});
|
||||||
|
|
||||||
|
async function importSQL() {
|
||||||
|
try {
|
||||||
|
let totalSuccess = 0;
|
||||||
|
let totalErrors = 0;
|
||||||
|
|
||||||
|
// Step 1: Import Devil Fruits
|
||||||
|
if (fs.existsSync('./scraped-data/devil-fruits.sql')) {
|
||||||
|
console.log('\n=== Importing Devil Fruits ===\n');
|
||||||
|
const devilFruitsSql = fs.readFileSync('./scraped-data/devil-fruits.sql', 'utf-8');
|
||||||
|
const dfStatements = devilFruitsSql.split(';\n\n').filter(s => s.trim());
|
||||||
|
|
||||||
|
console.log(`Found ${dfStatements.length} devil fruit statements\n`);
|
||||||
|
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < dfStatements.length; i++) {
|
||||||
|
const statement = dfStatements[i];
|
||||||
|
if (statement.trim()) {
|
||||||
|
try {
|
||||||
|
await client.execute(statement.trim() + ';');
|
||||||
|
successCount++;
|
||||||
|
process.stdout.write(`\rExecuted: ${successCount}/${dfStatements.length}`);
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
const valuesMatch = statement.match(/VALUES\s*\(([^)]+)\)/);
|
||||||
|
const values = valuesMatch ? valuesMatch[1] : 'N/A';
|
||||||
|
console.error(`\n✗ Error at statement ${i + 1}:`);
|
||||||
|
console.error(` Values: ${values}`);
|
||||||
|
console.error(` Message: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n\n✓ Devil Fruits imported!`);
|
||||||
|
console.log(` Success: ${successCount}`);
|
||||||
|
console.log(` Errors: ${errorCount}`);
|
||||||
|
|
||||||
|
totalSuccess += successCount;
|
||||||
|
totalErrors += errorCount;
|
||||||
|
} else {
|
||||||
|
console.log('\n⚠️ No devil-fruits.sql found, skipping...\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Import Characters
|
||||||
|
if (fs.existsSync('./scraped-data/characters.sql')) {
|
||||||
|
console.log('\n=== Importing Characters ===\n');
|
||||||
|
const charactersSql = fs.readFileSync('./scraped-data/characters.sql', 'utf-8');
|
||||||
|
const charStatements = charactersSql.split(';\n\n').filter(s => s.trim());
|
||||||
|
|
||||||
|
console.log(`Found ${charStatements.length} character statements\n`);
|
||||||
|
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < charStatements.length; i++) {
|
||||||
|
const statement = charStatements[i];
|
||||||
|
if (statement.trim()) {
|
||||||
|
try {
|
||||||
|
await client.execute(statement.trim() + ';');
|
||||||
|
successCount++;
|
||||||
|
process.stdout.write(`\rExecuted: ${successCount}/${charStatements.length}`);
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
const valuesMatch = statement.match(/VALUES\s*\(([^)]+)\)/);
|
||||||
|
const values = valuesMatch ? valuesMatch[1] : 'N/A';
|
||||||
|
console.error(`\n✗ Error at statement ${i + 1}:`);
|
||||||
|
console.error(` Values: ${values}`);
|
||||||
|
console.error(` Message: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n\n✓ Characters imported!`);
|
||||||
|
console.log(` Success: ${successCount}`);
|
||||||
|
console.log(` Errors: ${errorCount}`);
|
||||||
|
|
||||||
|
totalSuccess += successCount;
|
||||||
|
totalErrors += errorCount;
|
||||||
|
} else {
|
||||||
|
console.log('\n⚠️ No characters.sql found, skipping...\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n=== Total Import Summary ===`);
|
||||||
|
console.log(` Total Success: ${totalSuccess}`);
|
||||||
|
console.log(` Total Errors: ${totalErrors}\n`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('✗ Import failed:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
importSQL().catch(console.error);
|
||||||
672
scripts/scrape-onepiece.js
Normal file
672
scripts/scrape-onepiece.js
Normal file
@@ -0,0 +1,672 @@
|
|||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { createObjectCsvWriter } from 'csv-writer';
|
||||||
|
|
||||||
|
const FANDOM_BASE_URL = 'https://onepiece.fandom.com/fr/wiki';
|
||||||
|
const OUTPUT_DIR = './scraped-data';
|
||||||
|
const DEVIL_FRUIT_CONCURRENCY = 5;
|
||||||
|
const CHARACTER_CONCURRENCY = 10;
|
||||||
|
|
||||||
|
// Create output directory
|
||||||
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||||
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize string by removing accents and converting to lowercase
|
||||||
|
*/
|
||||||
|
function normalizeId(str) {
|
||||||
|
return decodeURIComponent(str)
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.replace(/[,:]/g, '')
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all devil fruits URLs from One Piece fandom
|
||||||
|
*/
|
||||||
|
async function fetchAllDevilFruitsUrl() {
|
||||||
|
try {
|
||||||
|
const url = `${FANDOM_BASE_URL}/Fruits_du_Démon`;
|
||||||
|
console.log('Fetching devil fruits list...');
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.01; Windows NT 5.0)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.text();
|
||||||
|
const $ = cheerio.load(data);
|
||||||
|
const devilFruits = [];
|
||||||
|
|
||||||
|
// Find the main navibox table
|
||||||
|
$('table.navibox.toccolours').each((mainTableIndex, mainTable) => {
|
||||||
|
const mainHeader = $(mainTable).find('th[colspan="3"]').first().find('span').last().text().trim();
|
||||||
|
if (mainHeader !== 'Fruits du Démon') return;
|
||||||
|
|
||||||
|
$(mainTable).find('table.collapsible').each((typeTableIndex, typeTable) => {
|
||||||
|
const typeHeader = $(typeTable).find('th[colspan="3"]').first().text().trim();
|
||||||
|
let type = null;
|
||||||
|
|
||||||
|
if (typeHeader.includes('Paramecia')) type = 'Paramecia';
|
||||||
|
else if (typeHeader.includes('Zoan')) type = 'Zoan';
|
||||||
|
else if (typeHeader.includes('Logia')) type = 'Logia';
|
||||||
|
else if (typeHeader.includes('Type Inconnu')) type = 'Unknown';
|
||||||
|
|
||||||
|
if (!type) return;
|
||||||
|
|
||||||
|
$(typeTable).find('tr.navibox-row').each((rowIndex, row) => {
|
||||||
|
const categoryHeader = $(row).find('th').text().trim();
|
||||||
|
|
||||||
|
if (!categoryHeader.includes('Canon') &&
|
||||||
|
!categoryHeader.includes('Standards') &&
|
||||||
|
!categoryHeader.includes('Antiques') &&
|
||||||
|
!categoryHeader.includes('Mythiques') &&
|
||||||
|
!categoryHeader.includes('Hors-Série')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all links in the row
|
||||||
|
$(row).find('td .hlist ul li a').each((linkIndex, link) => {
|
||||||
|
const name = $(link).text().trim();
|
||||||
|
const href = $(link).attr('href');
|
||||||
|
|
||||||
|
if (name && href && href.startsWith('/fr/wiki/')) {
|
||||||
|
// Clean the URL
|
||||||
|
const cleanUrl = href.replace('/fr/wiki/', '');
|
||||||
|
|
||||||
|
// Skip classification pages and category pages
|
||||||
|
if (cleanUrl.includes('Classification') ||
|
||||||
|
cleanUrl.includes('Catégorie:') ||
|
||||||
|
cleanUrl === 'Fruits_du_Démon_Artificiels' ||
|
||||||
|
cleanUrl === 'SMILE') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
devilFruits.push({
|
||||||
|
id: normalizeId(cleanUrl),
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
url: cleanUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Found ${devilFruits.length} devil fruits.`);
|
||||||
|
return devilFruits;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching devil fruits list:', error.message);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch devil fruit data from fandom using provided URL
|
||||||
|
*/
|
||||||
|
async function fetchDevilFruit(devilFruitUrl, devilFruitId, devilFruitName, devilFruitType) {
|
||||||
|
try {
|
||||||
|
console.log(`Fetching: ${devilFruitName}...`);
|
||||||
|
|
||||||
|
const response = await fetch(`${FANDOM_BASE_URL}/${devilFruitUrl}`, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.01; Windows NT 5.0)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.text();
|
||||||
|
const $ = cheerio.load(data);
|
||||||
|
|
||||||
|
// Extract devil fruit name from page title if different
|
||||||
|
const name = $('h1.mw-page-title-main').text().trim() || devilFruitName;
|
||||||
|
|
||||||
|
// Use the type from the list page
|
||||||
|
const type = devilFruitType;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: devilFruitId,
|
||||||
|
name,
|
||||||
|
type
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching ${devilFruitName}:`, error.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save devil fruits to JSON
|
||||||
|
*/
|
||||||
|
async function saveDevilFruitsToJSON(devilFruits) {
|
||||||
|
const filepath = `${OUTPUT_DIR}/devil-fruits.json`;
|
||||||
|
fs.writeFileSync(filepath, JSON.stringify(devilFruits, null, 2));
|
||||||
|
console.log(`✓ Saved to ${filepath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save devil fruits to SQL
|
||||||
|
*/
|
||||||
|
function saveDevilFruitsToSQL(devilFruits) {
|
||||||
|
const filepath = `${OUTPUT_DIR}/devil-fruits.sql`;
|
||||||
|
const escapeSql = (value) => (value ? `'${String(value).replace(/'/g, "''")}'` : 'NULL');
|
||||||
|
|
||||||
|
let sql = '';
|
||||||
|
|
||||||
|
devilFruits.forEach((df) => {
|
||||||
|
sql += `INSERT INTO devilFruit (id, name, type) \n`;
|
||||||
|
sql += `VALUES (${escapeSql(df.id)}, ${escapeSql(df.name)}, ${escapeSql(df.type)}) \n`;
|
||||||
|
sql += `ON CONFLICT(id) DO UPDATE SET \n`;
|
||||||
|
sql += ` name = excluded.name,\n`;
|
||||||
|
sql += ` type = excluded.type;\n\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(filepath, sql);
|
||||||
|
console.log(`✓ Saved to ${filepath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all cannon characters from One Piece fandom
|
||||||
|
*/
|
||||||
|
async function fetchAllCharactersUrl() {
|
||||||
|
try {
|
||||||
|
const url = `${FANDOM_BASE_URL}/Liste_des_Personnages_Canon`;
|
||||||
|
console.log('Fetching character list...');
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.01; Windows NT 5.0)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.text();
|
||||||
|
const $ = cheerio.load(data);
|
||||||
|
const characters = [];
|
||||||
|
$('table.wikitable tbody tr').each((index, element) => {
|
||||||
|
if (index === 0) return; // Skip header row
|
||||||
|
const charpictureUrl = $(element).find('td:nth-child(1) a img').attr('data-src') || $(element).find('td:nth-child(1) a img').attr('src');
|
||||||
|
const charLink = $(element).find('td:nth-child(2) a').attr('href');
|
||||||
|
const charName = $(element).find('td:nth-child(2) a').text().trim();
|
||||||
|
if (charLink) {
|
||||||
|
const cleanUrl = charLink.replace('/fr/wiki/', '');
|
||||||
|
characters.push({
|
||||||
|
id: normalizeId(cleanUrl),
|
||||||
|
name: charName,
|
||||||
|
url: cleanUrl,
|
||||||
|
pictureUrl: charpictureUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`Found ${characters.length} characters.`);
|
||||||
|
return characters;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching character list:', error.message);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch character data from fandom using provided URL
|
||||||
|
*/
|
||||||
|
async function fetchCharacter(characterUrl, characterId, characterName, characterpictureUrl) {
|
||||||
|
try {
|
||||||
|
console.log(`Fetching: ${characterName}...`);
|
||||||
|
|
||||||
|
const response = await fetch(`${FANDOM_BASE_URL}/${characterUrl}`, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.01; Windows NT 5.0)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Log response status for debugging
|
||||||
|
const data = await response.text();
|
||||||
|
|
||||||
|
const $ = cheerio.load(data);
|
||||||
|
|
||||||
|
// Extract character name
|
||||||
|
const name = $('h1.mw-page-title-main').text().trim() || characterName.replace(/_/g, ' ');
|
||||||
|
|
||||||
|
// Extract gender from the specific categories link
|
||||||
|
let gender = null;
|
||||||
|
if ($('.page-header__categories a[title="Catégorie:Personnages Masculins"]').length > 0) {
|
||||||
|
gender = 'Male';
|
||||||
|
} else if ($('.page-header__categories a[title="Catégorie:Personnages Féminins"]').length > 0) {
|
||||||
|
gender = 'Female';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract age
|
||||||
|
const age = extractAge($);
|
||||||
|
|
||||||
|
// Extract affiliations
|
||||||
|
const affiliations = extractAffiliations($);
|
||||||
|
|
||||||
|
// Extract devil fruit
|
||||||
|
const devilFruit = await extractDevilFruit($);
|
||||||
|
|
||||||
|
// Extract haki
|
||||||
|
let haki = [];
|
||||||
|
if ($('.page-header__categories a[title="Catégorie:Utilisateurs du Haki de l\'observation"]').length > 0) {
|
||||||
|
haki.push('Observation');
|
||||||
|
}
|
||||||
|
if ($('.page-header__categories a[title="Catégorie:Utilisateurs du Haki de l\'armement"]').length > 0) {
|
||||||
|
haki.push('Armament');
|
||||||
|
}
|
||||||
|
if ($('.page-header__categories a[title="Catégorie:Utilisateurs du Haki des rois"]').length > 0) {
|
||||||
|
haki.push('Conqueror');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract bounty
|
||||||
|
const bounty = extractBounty($);
|
||||||
|
|
||||||
|
// Extract height
|
||||||
|
const height = extractHeight($);
|
||||||
|
|
||||||
|
// Extract first appearance
|
||||||
|
const firstAppearance = extractFirstAppearance($);
|
||||||
|
|
||||||
|
// Extract origin
|
||||||
|
const origin = extractOrigin($);
|
||||||
|
|
||||||
|
// Extract image URL and clean it
|
||||||
|
let pictureUrl = characterpictureUrl;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: characterId,
|
||||||
|
name,
|
||||||
|
gender,
|
||||||
|
age,
|
||||||
|
height,
|
||||||
|
origin,
|
||||||
|
devilFruit,
|
||||||
|
affiliations,
|
||||||
|
bounty,
|
||||||
|
haki,
|
||||||
|
firstAppearance,
|
||||||
|
pictureUrl
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching ${characterName}:`, error.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract age from infobox
|
||||||
|
*/
|
||||||
|
function extractAge($) {
|
||||||
|
const div = $('[data-source="âge"] .pi-data-value');
|
||||||
|
if (div.length === 0) return null;
|
||||||
|
|
||||||
|
let text = div.html();
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
// Remove all sup blocks (citations)
|
||||||
|
text = text.replace(/<sup[^>]*>.*?<\/sup>/gi, '');
|
||||||
|
|
||||||
|
// Get the last element and extract only digits
|
||||||
|
const parts = text.split('<br');
|
||||||
|
const lastPart = parts[parts.length - 1];
|
||||||
|
let cleanText = lastPart.replace(/<[^>]*>/g, '').trim();
|
||||||
|
|
||||||
|
// Remove content with parentheses
|
||||||
|
cleanText = cleanText.replace(/\([^)]*\)/g, '');
|
||||||
|
|
||||||
|
const digitsOnly = cleanText.replace(/\D/g, '');
|
||||||
|
return digitsOnly || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract affiliations from infobox
|
||||||
|
*/
|
||||||
|
function extractAffiliations($) {
|
||||||
|
const div = $('[data-source="affiliation"] .pi-data-value');
|
||||||
|
if (div.length === 0) return [];
|
||||||
|
|
||||||
|
const cleanedDiv = div.clone();
|
||||||
|
cleanedDiv.find('sup').remove();
|
||||||
|
|
||||||
|
let text = cleanedDiv.html();
|
||||||
|
if (!text) return [];
|
||||||
|
|
||||||
|
// Extract all link values
|
||||||
|
const linkValues = cleanedDiv.find('a').map((i, el) => $(el).text().trim()).get();
|
||||||
|
if (linkValues.length > 0) {
|
||||||
|
return linkValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to parsing text
|
||||||
|
const cleanText = text.replace(/<[^>]*>/g, '').trim();
|
||||||
|
const parts = cleanText.split(/\s*\n\s*|\s*;\s*|\s*,\s*/).filter(Boolean);
|
||||||
|
return parts.length > 0 ? parts : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract devil fruit from infobox
|
||||||
|
*/
|
||||||
|
async function extractDevilFruit($) {
|
||||||
|
const link = $('[data-source="dfnom"] .pi-data-value a').first();
|
||||||
|
if (link.length === 0) return null;
|
||||||
|
|
||||||
|
const href = link.attr('href');
|
||||||
|
if (!href || !href.startsWith('/fr/wiki/')) return null;
|
||||||
|
|
||||||
|
const cleanUrl = href.replace('/fr/wiki/', '');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch the page to follow redirects
|
||||||
|
const response = await fetch(`${FANDOM_BASE_URL}/${cleanUrl}`, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.01; Windows NT 5.0)',
|
||||||
|
},
|
||||||
|
redirect: 'follow' // Explicitly follow redirects
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if response was a redirect (301, 302, etc.)
|
||||||
|
if (response.status === 301 || response.status === 302) {
|
||||||
|
// Use the final redirected URL
|
||||||
|
const finalUrl = new URL(response.url);
|
||||||
|
const pathname = finalUrl.pathname;
|
||||||
|
const finalPath = pathname.replace('/fr/wiki/', '');
|
||||||
|
if (finalPath) {
|
||||||
|
return normalizeId(finalPath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use the current URL if no redirect
|
||||||
|
const finalUrl = new URL(response.url);
|
||||||
|
const pathname = finalUrl.pathname;
|
||||||
|
const finalPath = pathname.replace('/fr/wiki/', '');
|
||||||
|
if (finalPath) {
|
||||||
|
return normalizeId(finalPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching devil fruit page: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to the original href
|
||||||
|
return normalizeId(cleanUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract bounty from infobox
|
||||||
|
*/
|
||||||
|
function extractBounty($) {
|
||||||
|
const div = $('[data-source="prime"] .pi-data-value');
|
||||||
|
if (div.length === 0) return null;
|
||||||
|
|
||||||
|
let text = div.html();
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
// Remove all sup blocks (citations)
|
||||||
|
text = text.replace(/<sup[^>]*>.*?<\/sup>/gi, '');
|
||||||
|
|
||||||
|
// Extract the first value before any <br> tag
|
||||||
|
const firstValue = text.split('<br')[0].trim();
|
||||||
|
let cleanText = firstValue.replace(/<[^>]*>/g, '').trim();
|
||||||
|
|
||||||
|
// Remove spaces and dots
|
||||||
|
cleanText = cleanText.replace(/[\s.]/g, '');
|
||||||
|
|
||||||
|
return cleanText || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract height from infobox
|
||||||
|
*/
|
||||||
|
function extractHeight($) {
|
||||||
|
const div = $('[data-source="taille"] .pi-data-value');
|
||||||
|
if (div.length === 0) return null;
|
||||||
|
|
||||||
|
let text = div.html();
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
// Remove all sup blocks (citations)
|
||||||
|
text = text.replace(/<sup[^>]*>.*?<\/sup>/gi, '');
|
||||||
|
|
||||||
|
// Extract the last value after any <br> tag
|
||||||
|
const lastValue = text.split('<br>').pop().trim();
|
||||||
|
let cleanText = lastValue.replace(/<[^>]*>/g, '').trim();
|
||||||
|
|
||||||
|
// Remove content with parentheses
|
||||||
|
cleanText = cleanText.replace(/\([^)]*\)/g, '');
|
||||||
|
|
||||||
|
// Normalize units for meters or centimeters
|
||||||
|
const normalized = cleanText.toLowerCase().replace(/\s/g, '');
|
||||||
|
if (normalized.includes('cm')) {
|
||||||
|
const digitsOnly = normalized.replace(/\D/g, '');
|
||||||
|
return digitsOnly || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized.includes('m')) {
|
||||||
|
const parts = normalized.split('m').filter(Boolean);
|
||||||
|
return parts.length > 0 ? parts.join('.') : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized.replace(/\D/g, '') || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract first appearance from infobox
|
||||||
|
*/
|
||||||
|
function extractFirstAppearance($) {
|
||||||
|
const div = $('[data-source="première"] .pi-data-value');
|
||||||
|
if (div.length === 0) return null;
|
||||||
|
|
||||||
|
let text = div.html();
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
// Remove all sup blocks (citations)
|
||||||
|
text = text.replace(/<sup[^>]*>.*?<\/sup>/gi, '');
|
||||||
|
|
||||||
|
// Extract digits after "Chapitre"
|
||||||
|
const cleanText = text.replace(/<[^>]*>/g, '').trim();
|
||||||
|
const match = cleanText.match(/Chapitre\s+(\d+)/i);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract origin from infobox
|
||||||
|
*/
|
||||||
|
function extractOrigin($) {
|
||||||
|
const div = $('[data-source="origine"] .pi-data-value');
|
||||||
|
if (div.length === 0) return null;
|
||||||
|
|
||||||
|
let text = div.html();
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
// Remove all sup blocks (citations)
|
||||||
|
text = text.replace(/<sup[^>]*>.*?<\/sup>/gi, '');
|
||||||
|
|
||||||
|
// Extract the first value before any <br> tag
|
||||||
|
const firstValue = text.split('<br')[0].trim();
|
||||||
|
let cleanText = firstValue.replace(/<[^>]*>/g, '').trim();
|
||||||
|
|
||||||
|
// Remove content with parentheses
|
||||||
|
cleanText = cleanText.replace(/\([^)]*\)/g, '').trim();
|
||||||
|
|
||||||
|
return cleanText || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save data to JSON
|
||||||
|
*/
|
||||||
|
async function saveToJSON(characters) {
|
||||||
|
const filepath = `${OUTPUT_DIR}/characters.json`;
|
||||||
|
fs.writeFileSync(filepath, JSON.stringify(characters, null, 2));
|
||||||
|
console.log(`✓ Saved to ${filepath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save data to CSV
|
||||||
|
*/
|
||||||
|
async function saveToCSV(characters) {
|
||||||
|
const filepath = `${OUTPUT_DIR}/characters.csv`;
|
||||||
|
const csvWriter = createObjectCsvWriter({
|
||||||
|
path: filepath,
|
||||||
|
header: [
|
||||||
|
{ id: 'id', title: 'ID' },
|
||||||
|
{ id: 'name', title: 'Name' },
|
||||||
|
{ id: 'gender', title: 'Gender' },
|
||||||
|
{ id: 'age', title: 'Age' },
|
||||||
|
{ id: 'height', title: 'Height' },
|
||||||
|
{ id: 'origin', title: 'Origin' },
|
||||||
|
{ id: 'devilFruit', title: 'Devil Fruit' },
|
||||||
|
{ id: 'affiliations', title: 'Affiliations' },
|
||||||
|
{ id: 'bounty', title: 'Bounty' },
|
||||||
|
{ id: 'haki', title: 'Haki' },
|
||||||
|
{ id: 'firstAppearance', title: 'First Appearance' },
|
||||||
|
{ id: 'pictureUrl', title: 'Image URL' }
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const records = characters
|
||||||
|
.filter((c) => c !== null)
|
||||||
|
.map((c) => ({
|
||||||
|
id: c.id || '',
|
||||||
|
name: c.name || '',
|
||||||
|
gender: c.gender || '',
|
||||||
|
age: c.age || '',
|
||||||
|
height: c.height || '',
|
||||||
|
origin: c.origin || '',
|
||||||
|
devilFruit: c.devilFruit || '',
|
||||||
|
affiliations: Array.isArray(c.affiliations) ? c.affiliations.join(', ') : (c.affiliations || ''),
|
||||||
|
bounty: c.bounty || '',
|
||||||
|
haki: Array.isArray(c.haki) ? c.haki.join(', ') : (c.haki || ''),
|
||||||
|
firstAppearance: c.firstAppearance || '',
|
||||||
|
pictureUrl: c.pictureUrl || ''
|
||||||
|
}));
|
||||||
|
|
||||||
|
await csvWriter.writeRecords(records);
|
||||||
|
console.log(`✓ Saved to ${filepath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save data to SQL
|
||||||
|
*/
|
||||||
|
function saveToSQL(characters) {
|
||||||
|
const filepath = `${OUTPUT_DIR}/characters.sql`;
|
||||||
|
const escapeSql = (value) => (value ? `'${String(value).replace(/'/g, "''")}'` : 'NULL');
|
||||||
|
|
||||||
|
let sql = '';
|
||||||
|
|
||||||
|
characters
|
||||||
|
.filter((c) => c !== null)
|
||||||
|
.forEach((c) => {
|
||||||
|
const affiliations = Array.isArray(c.affiliations) ? c.affiliations.join(', ') : c.affiliations;
|
||||||
|
const hakiValue = Array.isArray(c.haki) && c.haki.length > 0 ? JSON.stringify(c.haki) : null;
|
||||||
|
|
||||||
|
sql += `INSERT INTO character (id, name, gender, age, height, origin, devilFruit, affiliations, bounty, haki, firstAppearance, pictureUrl) \n`;
|
||||||
|
sql += `VALUES (${escapeSql(c.id)}, ${escapeSql(c.name)}, ${escapeSql(c.gender)}, ${escapeSql(c.age)}, ${escapeSql(c.height)}, ${escapeSql(c.origin)}, ${escapeSql(c.devilFruit)}, ${escapeSql(affiliations)}, ${escapeSql(c.bounty)}, ${escapeSql(hakiValue)}, ${escapeSql(c.firstAppearance)}, ${escapeSql(c.pictureUrl)}) \n`;
|
||||||
|
sql += `ON CONFLICT(id) DO UPDATE SET \n`;
|
||||||
|
sql += ` name = excluded.name,\n`;
|
||||||
|
sql += ` gender = excluded.gender,\n`;
|
||||||
|
sql += ` age = excluded.age,\n`;
|
||||||
|
sql += ` height = excluded.height,\n`;
|
||||||
|
sql += ` origin = excluded.origin,\n`;
|
||||||
|
sql += ` devilFruit = excluded.devilFruit,\n`;
|
||||||
|
sql += ` affiliations = excluded.affiliations,\n`;
|
||||||
|
sql += ` bounty = excluded.bounty,\n`;
|
||||||
|
sql += ` haki = excluded.haki,\n`;
|
||||||
|
sql += ` firstAppearance = excluded.firstAppearance,\n`;
|
||||||
|
sql += ` pictureUrl = excluded.pictureUrl;\n\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(filepath, sql);
|
||||||
|
console.log(`✓ Saved to ${filepath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main execution
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const format = process.argv[2] || 'all'; // json, csv, sql, or all
|
||||||
|
|
||||||
|
console.log(`\nOne Piece Scraper - Mode: ${format}\n`);
|
||||||
|
|
||||||
|
// Step 1: Scraping Devil Fruits
|
||||||
|
console.log('=== Step 1: Scraping Devil Fruits ===\n');
|
||||||
|
const devilFruitList = await fetchAllDevilFruitsUrl();
|
||||||
|
|
||||||
|
if (devilFruitList.length === 0) {
|
||||||
|
console.warn('No devil fruits found, continuing with characters...\n');
|
||||||
|
} else {
|
||||||
|
const devilFruits = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < devilFruitList.length; i += DEVIL_FRUIT_CONCURRENCY) {
|
||||||
|
const batch = devilFruitList.slice(i, i + DEVIL_FRUIT_CONCURRENCY);
|
||||||
|
const results = await Promise.all(
|
||||||
|
batch.map((df) => fetchDevilFruit(df.url, df.id, df.name, df.type))
|
||||||
|
);
|
||||||
|
|
||||||
|
results.filter(Boolean).forEach((data) => {
|
||||||
|
console.table({
|
||||||
|
ID: data.id,
|
||||||
|
Name: data.name,
|
||||||
|
Type: data.type
|
||||||
|
});
|
||||||
|
|
||||||
|
devilFruits.push(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n✓ Scraped ${devilFruits.length} devil fruits\n`);
|
||||||
|
|
||||||
|
if (format === 'json' || format === 'all') {
|
||||||
|
await saveDevilFruitsToJSON(devilFruits);
|
||||||
|
}
|
||||||
|
if (format === 'sql' || format === 'all') {
|
||||||
|
saveDevilFruitsToSQL(devilFruits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Scraping Characters
|
||||||
|
console.log('=== Step 2: Scraping Characters ===\n');
|
||||||
|
const characterList = await fetchAllCharactersUrl();
|
||||||
|
|
||||||
|
if (characterList.length === 0) {
|
||||||
|
console.error('No characters found. Exiting.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const characters = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < characterList.length; i += CHARACTER_CONCURRENCY) {
|
||||||
|
const batch = characterList.slice(i, i + CHARACTER_CONCURRENCY);
|
||||||
|
const results = await Promise.all(
|
||||||
|
batch.map((char) => fetchCharacter(char.url, char.id, char.name, char.pictureUrl))
|
||||||
|
);
|
||||||
|
results.filter(Boolean).forEach((data) => {
|
||||||
|
console.table({
|
||||||
|
ID: data.id,
|
||||||
|
Name: data.name,
|
||||||
|
Gender: data.gender,
|
||||||
|
Age: data.age,
|
||||||
|
Affiliations: data.affiliations.join(', '),
|
||||||
|
DevilFruit: data.devilFruit,
|
||||||
|
Haki: data.haki.join(', '),
|
||||||
|
Height: data.height,
|
||||||
|
Bounty: data.bounty,
|
||||||
|
Origin: data.origin,
|
||||||
|
FirstAppearance: data.firstAppearance,
|
||||||
|
pictureUrl: data.pictureUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
characters.push(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n✓ Scraped ${characters.length} characters\n`);
|
||||||
|
|
||||||
|
if (format === 'json' || format === 'all') {
|
||||||
|
await saveToJSON(characters);
|
||||||
|
}
|
||||||
|
if (format === 'csv' || format === 'all') {
|
||||||
|
await saveToCSV(characters);
|
||||||
|
}
|
||||||
|
if (format === 'sql' || format === 'all') {
|
||||||
|
saveToSQL(characters);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✓ Done!\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
@@ -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')
|
id: text('id')
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => crypto.randomUUID()),
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
title: text('title').notNull(),
|
characterId: text('characterId').references(() => character.id),
|
||||||
priority: integer('priority').notNull().default(1)
|
date: integer('date'),
|
||||||
|
createdAt: integer('createdAt').notNull().$default(() => Date.now()),
|
||||||
|
updatedAt: integer('updatedAt').notNull().$default(() => Date.now()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export * from './auth.schema';
|
export * from './auth.schema';
|
||||||
|
|||||||
@@ -1,2 +1,63 @@
|
|||||||
<h1>Welcome to SvelteKit</h1>
|
<svelte:head>
|
||||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
<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