Refactor database schema and update scraping logic for One Piece characters and arcs

- Updated database schema to include French names and adjusted field names for consistency.
- Modified scraping script to fetch and store French names for arcs and characters.
- Improved API calls to handle redirects and fetch additional data for characters.
- Enhanced data extraction methods for character attributes and devil fruits.
- Cleaned up code for better readability and maintainability.
This commit is contained in:
2026-03-14 01:23:29 +01:00
parent a91b298ee5
commit a041a8caf5
24 changed files with 844 additions and 11193 deletions

View File

@@ -17,13 +17,14 @@ export const config = sqliteTable('config', {
export const arc = sqliteTable('arc', {
id: text('id').primaryKey(),
name: text('name').notNull(),
startChapter: integer('startChapter').notNull(),
endChapter: integer('endChapter'),
frName: text('fr_name'),
startChapter: integer('start_chapter').notNull(),
endChapter: integer('end_chapter'),
url: text('url')
});
// Define the devil fruit table schema
export const devilFruit = sqliteTable('devilFruit', {
export const devilFruit = sqliteTable('devil_fruit', {
id: text('id').primaryKey(),
name: text('name').notNull().unique(),
type: text('type').$type<DevilFruitType>(),
@@ -34,91 +35,101 @@ export const devilFruit = sqliteTable('devilFruit', {
export const character = sqliteTable('character', {
id: text('id').primaryKey(),
name: text('name').notNull(),
frName: text('fr_name'),
gender: text('gender'),
age: integer('age'),
affiliations: text('affiliations', { mode: 'json' }).$type<string[]>(),
devilFruitId: text('devilFruitId').references(() => devilFruit.id),
hakiObservation: integer('hakiObservation', { mode: 'boolean' }).default(false),
hakiArmament: integer('hakiArmament', { mode: 'boolean' }).default(false),
hakiConqueror: integer('hakiConqueror', { mode: 'boolean' }).default(false),
devilFruitId: text('devil_fruit_id').references(() => devilFruit.id),
hakiObservation: integer('haki_observation', { mode: 'boolean' }).default(false),
hakiArmament: integer('haki_armament', { mode: 'boolean' }).default(false),
hakiConqueror: integer('haki_conqueror', { mode: 'boolean' }).default(false),
bounty: integer('bounty').default(0),
height: real('height'),
origin: text('origin'),
firstAppearance: integer('firstAppearance').notNull(),
pictureUrl: text('pictureUrl'),
frOrigin: text('fr_origin'),
firstAppearance: integer('first_appearance').notNull(),
pictureUrl: text('picture_url'),
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
frEpithets: text('fr_epithets', { mode: 'json' }).$type<string[]>(),
status: text('status').$type<Status | null>(),
arcId: text('arcId').references(() => arc.id),
arcId: text('arc_id').references(() => arc.id, { onDelete: 'set null' }),
url: text('url'),
isInDailyMode: integer('isInDailyMode', { mode: 'boolean' }).default(false)
frUrl: text('fr_url'),
isInDailyMode: integer('is_in_daily_mode', { mode: 'boolean' }).default(false)
});
// Define the character override table schema
export const characterOverride = sqliteTable('characterOverride', {
characterId: text('characterId').primaryKey().references(() => character.id),
export const characterOverride = sqliteTable('character_override', {
characterId: text('character_id').primaryKey().references(() => character.id, { onDelete: 'cascade' }),
name: text('name'),
gender: text('gender'),
age: integer('age'),
affiliations: text('affiliations', { mode: 'json' }).$type<string[]>(),
devilFruitId: text('devilFruitId').references(() => devilFruit.id),
hakiObservation: integer('hakiObservation', { mode: 'boolean' }),
hakiArmament: integer('hakiArmament', { mode: 'boolean' }),
hakiConqueror: integer('hakiConqueror', { mode: 'boolean' }),
devilFruitId: text('devil_fruit_id').references(() => devilFruit.id, { onDelete: 'set null' }),
hakiObservation: integer('haki_observation', { mode: 'boolean' }),
hakiArmament: integer('haki_armament', { mode: 'boolean' }),
hakiConqueror: integer('haki_conqueror', { mode: 'boolean' }),
bounty: integer('bounty'),
height: real('height'),
origin: text('origin'),
firstAppearance: integer('firstAppearance'),
pictureUrl: text('pictureUrl'),
firstAppearance: integer('first_appearance'),
pictureUrl: text('picture_url'),
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
status: text('status').$type<Status | null>(),
arcId: text('arcId').references(() => arc.id),
arcId: text('arc_id').references(() => arc.id, { onDelete: 'set null' }),
url: text('url'),
frUrl: text('fr_url'),
notes: text('notes')
});
// Define the character scrape validation table schema
export const characterScrapeValidation = sqliteTable('characterScrapeValidation', {
export const characterScrapeValidation = sqliteTable('character_scrape_validation', {
id: text('id').primaryKey(),
name: text('name').notNull(),
frName: text('fr_name'),
gender: text('gender'),
age: integer('age'),
affiliations: text('affiliations', { mode: 'json' }).$type<string[]>(),
devilFruitId: text('devilFruitId').references(() => devilFruit.id),
hakiObservation: integer('hakiObservation', { mode: 'boolean' }).default(false),
hakiArmament: integer('hakiArmament', { mode: 'boolean' }).default(false),
hakiConqueror: integer('hakiConqueror', { mode: 'boolean' }).default(false),
devilFruitId: text('devil_fruit_id').references(() => devilFruit.id, { onDelete: 'set null' }),
hakiObservation: integer('haki_observation', { mode: 'boolean' }).default(false),
hakiArmament: integer('haki_armament', { mode: 'boolean' }).default(false),
hakiConqueror: integer('haki_conqueror', { mode: 'boolean' }).default(false),
bounty: integer('bounty'),
height: real('height'),
origin: text('origin'),
firstAppearance: integer('firstAppearance').notNull(),
pictureUrl: text('pictureUrl'),
frOrigin: text('fr_origin'),
firstAppearance: integer('first_appearance').notNull(),
pictureUrl: text('picture_url'),
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
frEpithets: text('fr_epithets', { mode: 'json' }).$type<string[]>(),
status: text('status').$type<Status | null>(),
arcId: text('arcId').references(() => arc.id),
url: text('url')
arcId: text('arc_id').references(() => arc.id, { onDelete: 'set null' }),
url: text('url'),
frUrl: text('fr_url')
});
// Define the caracter history table schema
export const characterHistory = sqliteTable('characterHistory', {
// Define the character history table schema
export const characterHistory = sqliteTable('character_history', {
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
characterId: text('characterId').references(() => character.id),
characterId: text('character_id').references(() => character.id, { onDelete: 'cascade' }),
date: integer('date').notNull().unique(),
won: integer('won').notNull().default(0),
createdAt: integer('createdAt').notNull().$default(() => Date.now()),
updatedAt: integer('updatedAt').notNull().$default(() => Date.now()),
createdAt: integer('created_at').notNull().$default(() => Date.now()),
updatedAt: integer('updated_at').notNull().$default(() => Date.now()),
});
// Define the user character history table schema
export const userCharacterHistory = sqliteTable('userCharacterHistory', {
export const userCharacterHistory = sqliteTable('user_character_history', {
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
userId: text('userId').references(() => user.id),
characterHistoryId: text('characterHistoryId').references(() => characterHistory.id),
tryCount: integer('tryCount').notNull(),
createdAt: integer('createdAt').notNull().$default(() => Date.now())
userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
characterHistoryId: text('character_history_id').references(() => characterHistory.id, { onDelete: 'cascade' }),
tryCount: integer('try_count').notNull(),
triedCharacterIds: text('tried_character_ids', { mode: 'json' }).$type<string[]>(),
createdAt: integer('created_at').notNull().$default(() => Date.now())
}, (table) => [
unique().on(table.userId, table.characterHistoryId)
]);
@@ -128,15 +139,15 @@ export const friendship = sqliteTable('friendship', {
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
requesterId: text('requesterId')
requesterId: text('requester_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
addresseeId: text('addresseeId')
addresseeId: text('addressee_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
status: text('status').$type<FriendshipStatus>().notNull().default('pending'),
createdAt: integer('createdAt').notNull().$default(() => Date.now()),
updatedAt: integer('updatedAt').notNull().$default(() => Date.now()),
createdAt: integer('created_at').notNull().$default(() => Date.now()),
updatedAt: integer('updated_at').notNull().$default(() => Date.now()),
}, (table) => [
unique().on(table.requesterId, table.addresseeId)
]);