Compare commits
19 Commits
1de2439a8a
...
df3577f673
| Author | SHA1 | Date |
|---|---|---|
|
|
df3577f673 | |
|
|
871861e66e | |
|
|
1ee895ab42 | |
|
|
3f58114091 | |
|
|
878bcc0a16 | |
|
|
e924fdfe38 | |
|
|
5e6f296c56 | |
|
|
ab2778c626 | |
|
|
7a11c5382c | |
|
|
4885479ac6 | |
|
|
fd0603f916 | |
|
|
0771d5ebd1 | |
|
|
598cf54bc5 | |
|
|
9352b3f0a1 | |
|
|
a30f394ef7 | |
|
|
32439b41f6 | |
|
|
b8f547d3e9 | |
|
|
3c412d1cbe | |
|
|
735dfb6980 |
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<Label class="my-2 flex flex-1 items-center justify-between flex-col md:flex-row">
|
||||
<span class="pb-1 md:p-0">{{ label }}</span>
|
||||
<ComboboxRoot v-model:model-value="model" v-model:open="open" :multiple="multiple">
|
||||
<ComboboxAnchor :disabled="disabled" class="mx-4 inline-flex min-w-[150px] items-center justify-between px-3 text-sm font-semibold leading-none h-8 gap-1
|
||||
bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 outline-none data-[placeholder]:font-normal
|
||||
data-[placeholder]:text-light-50 dark:data-[placeholder]:text-dark-50 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40
|
||||
hover:border-light-50 dark:hover:border-dark-50">
|
||||
<ComboboxTrigger class="flex flex-1 justify-between !cursor-pointer">
|
||||
<span v-if="!multiple">{{ model !== undefined ? options.find(e => e[1] === model)![0] : "" }}</span>
|
||||
<span class="flex gap-2" v-else><span v-if="model !== undefined">{{ options.find(e => e[1] === (model as T[])[0]) !== undefined ? options.find(e => e[1] === (model as T[])[0])![0] : undefined }}</span><span v-if="model !== undefined && (model as T[]).length > 1">{{((model as T[]).length > 1 ? `+${(model as T[]).length - 1}` : "") }}</span></span>
|
||||
<Icon icon="radix-icons:caret-down" class="h-4 w-4" />
|
||||
</ComboboxTrigger>
|
||||
</ComboboxAnchor>
|
||||
|
||||
<ComboboxPortal :disabled="disabled">
|
||||
<ComboboxContent :position="position" align="start" class="min-w-[150px] bg-light-20 dark:bg-dark-20 will-change-[opacity,transform] z-50">
|
||||
<ComboboxViewport>
|
||||
<ComboboxItem v-for="[label, value] of options" :value="value" :disabled="disabled" class="text-base py-2 leading-none text-light-60 dark:text-dark-60 flex items-center px-6 relative Combobox-none data-[disabled]:text-light-50 dark:data-[disabled]:text-dark-50 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-light-30 dark:data-[highlighted]:bg-dark-30 data-[highlighted]:text-light-100 dark:data-[highlighted]:text-dark-100">
|
||||
<span class="">{{ label }}</span>
|
||||
<ComboboxItemIndicator class="absolute left-1 w-4 inline-flex items-center justify-center">
|
||||
<Icon icon="radix-icons:check" />
|
||||
</ComboboxItemIndicator>
|
||||
</ComboboxItem>
|
||||
</ComboboxViewport>
|
||||
</ComboboxContent>
|
||||
</ComboboxPortal>
|
||||
</ComboboxRoot>
|
||||
</Label>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T extends string | number | boolean | Record<string, any>">
|
||||
import { ComboboxInput, ComboboxTrigger, ComboboxViewport, ComboboxContent, ComboboxPortal, ComboboxRoot } from 'radix-vue'
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
const { disabled = false, position = 'popper', multiple = false } = defineProps<{
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
position?: 'inline' | 'popper'
|
||||
label?: string
|
||||
multiple?: boolean
|
||||
options: Array<[string, T]>
|
||||
}>();
|
||||
const open = ref(false);
|
||||
const model = defineModel<T | T[]>();
|
||||
</script>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<Label class="my-2 flex flex-1 items-center justify-between flex-col md:flex-row">
|
||||
<span class="pb-1 md:p-0">{{ label }}</span>
|
||||
<SelectRoot v-model="model">
|
||||
<SelectRoot v-model="model" :default-value="defaultValue">
|
||||
<SelectTrigger :disabled="disabled" class="mx-4 inline-flex min-w-[160px] items-center justify-between px-3 text-sm font-semibold leading-none h-8 gap-1
|
||||
bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 outline-none data-[placeholder]:font-normal
|
||||
data-[placeholder]:text-light-50 dark:data-[placeholder]:text-dark-50 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40
|
||||
|
|
@ -31,11 +31,12 @@
|
|||
<script setup lang="ts">
|
||||
import { SelectContent, SelectPortal, SelectRoot, SelectScrollDownButton, SelectScrollUpButton, SelectTrigger, SelectValue, SelectViewport } from 'radix-vue'
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
const { placeholder, disabled = false, position = 'popper', label } = defineProps<{
|
||||
const { disabled = false, position = 'popper' } = defineProps<{
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
position?: 'item-aligned' | 'popper'
|
||||
label?: string
|
||||
defaultValue?: string
|
||||
}>();
|
||||
const model = defineModel<string>();
|
||||
</script>
|
||||
|
|
@ -12,7 +12,7 @@ import { Icon } from '@iconify/vue/dist/iconify.js';
|
|||
import { SelectItem, SelectItemIndicator, SelectItemText } from 'radix-vue'
|
||||
const { disabled = false, value } = defineProps<{
|
||||
disabled?: boolean
|
||||
value: NonNullable<any>
|
||||
value: NonNullable<string>
|
||||
label: string
|
||||
}>();
|
||||
</script>
|
||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
82
db/schema.ts
82
db/schema.ts
|
|
@ -1,5 +1,6 @@
|
|||
import { relations } from 'drizzle-orm';
|
||||
import { int, text, sqliteTable, type SQLiteTableExtraConfig, primaryKey, blob } from 'drizzle-orm/sqlite-core';
|
||||
import { int, text, sqliteTable, primaryKey, blob } from 'drizzle-orm/sqlite-core';
|
||||
import { ABILITIES, MAIN_STATS } from '../types/character';
|
||||
|
||||
export const usersTable = sqliteTable("users", {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
|
|
@ -20,20 +21,12 @@ export const userSessionsTable = sqliteTable("user_sessions", {
|
|||
id: int().notNull(),
|
||||
user_id: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
timestamp: int({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
|
||||
}, (table): SQLiteTableExtraConfig => {
|
||||
return {
|
||||
pk: primaryKey({ columns: [table.id, table.user_id] }),
|
||||
}
|
||||
});
|
||||
}, (table) => [primaryKey({ columns: [table.id, table.user_id] })]);
|
||||
|
||||
export const userPermissionsTable = sqliteTable("user_permissions", {
|
||||
id: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
permission: text().notNull(),
|
||||
}, (table): SQLiteTableExtraConfig => {
|
||||
return {
|
||||
pk: primaryKey({ columns: [table.id, table.permission] }),
|
||||
}
|
||||
});
|
||||
}, (table) => [primaryKey({ columns: [table.id, table.permission] })]);
|
||||
|
||||
export const explorerContentTable = sqliteTable("explorer_content", {
|
||||
path: text().primaryKey(),
|
||||
|
|
@ -57,9 +50,47 @@ export const characterTable = sqliteTable("character", {
|
|||
id: int().primaryKey({ autoIncrement: true }),
|
||||
name: text().notNull(),
|
||||
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
progress: text({ mode: 'json' }).notNull(),
|
||||
people: int().notNull(),
|
||||
level: int().notNull().default(1),
|
||||
aspect: int(),
|
||||
notes: text(),
|
||||
health: int().notNull().default(0),
|
||||
mana: int().notNull().default(0),
|
||||
|
||||
visibility: text({ enum: ['private', 'public'] }).notNull().default('private'),
|
||||
thumbnail: blob(),
|
||||
})
|
||||
});
|
||||
|
||||
export const characterTrainingTable = sqliteTable("character_training", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
stat: text({ enum: MAIN_STATS }).notNull(),
|
||||
level: int().notNull(),
|
||||
choice: int().notNull(),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.stat, table.level] })]);
|
||||
|
||||
export const characterLevelingTable = sqliteTable("character_leveling", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
level: int().notNull(),
|
||||
choice: int().notNull(),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.level] })]);
|
||||
|
||||
export const characterAbilitiesTable = sqliteTable("character_abilities", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
ability: text({ enum: ABILITIES }).notNull(),
|
||||
value: int().notNull().default(0),
|
||||
max: int().notNull().default(0),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.ability] })]);
|
||||
|
||||
export const characterModifiersTable = sqliteTable("character_modifiers", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
modifier: text({ enum: MAIN_STATS }).notNull(),
|
||||
value: int().notNull().default(0),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.modifier] })]);
|
||||
|
||||
export const characterSpellsTable = sqliteTable("character_spell", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
value: text().notNull(),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.value] })]);
|
||||
|
||||
export const usersRelation = relations(usersTable, ({ one, many }) => ({
|
||||
data: one(usersDataTable, { fields: [usersTable.id], references: [usersDataTable.id], }),
|
||||
|
|
@ -79,6 +110,27 @@ export const userPermissionsRelation = relations(userPermissionsTable, ({ one })
|
|||
export const explorerContentRelation = relations(explorerContentTable, ({ one }) => ({
|
||||
users: one(usersTable, { fields: [explorerContentTable.owner], references: [usersTable.id], }),
|
||||
}));
|
||||
export const characterRelation = relations(characterTable, ({ one }) => ({
|
||||
users: one(usersTable, { fields: [characterTable.owner], references: [usersTable.id], }),
|
||||
export const characterRelation = relations(characterTable, ({ one, many }) => ({
|
||||
user: one(usersTable, { fields: [characterTable.owner], references: [usersTable.id], }),
|
||||
training: many(characterTrainingTable),
|
||||
levels: many(characterLevelingTable),
|
||||
abilities: many(characterAbilitiesTable),
|
||||
modifiers: many(characterModifiersTable),
|
||||
spells: many(characterSpellsTable)
|
||||
}));
|
||||
|
||||
export const characterTrainingRelation = relations(characterTrainingTable, ({ one }) => ({
|
||||
character: one(characterTable, { fields: [characterTrainingTable.character], references: [characterTable.id] })
|
||||
}));
|
||||
export const characterLevelingRelation = relations(characterLevelingTable, ({ one }) => ({
|
||||
character: one(characterTable, { fields: [characterLevelingTable.character], references: [characterTable.id] })
|
||||
}));
|
||||
export const characterAbilitiesRelation = relations(characterAbilitiesTable, ({ one }) => ({
|
||||
character: one(characterTable, { fields: [characterAbilitiesTable.character], references: [characterTable.id] })
|
||||
}));
|
||||
export const characterModifierRelation = relations(characterModifiersTable, ({ one }) => ({
|
||||
character: one(characterTable, { fields: [characterModifiersTable.character], references: [characterTable.id] })
|
||||
}));
|
||||
export const characterSpellsRelation = relations(characterSpellsTable, ({ one }) => ({
|
||||
character: one(characterTable, { fields: [characterSpellsTable.character], references: [characterTable.id] })
|
||||
}));
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `character` ADD `values` text DEFAULT '{}' NOT NULL;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `character` ADD `visibility` text DEFAULT 'private' NOT NULL;
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
CREATE TABLE `character_abilities` (
|
||||
`character` integer NOT NULL,
|
||||
`ability` text NOT NULL,
|
||||
`value` integer DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY(`character`, `ability`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `character_leveling` (
|
||||
`character` integer NOT NULL,
|
||||
`level` integer NOT NULL,
|
||||
`choice` integer NOT NULL,
|
||||
PRIMARY KEY(`character`, `level`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `character_modifiers` (
|
||||
`character` integer NOT NULL,
|
||||
`modifier` text NOT NULL,
|
||||
`value` integer DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY(`character`, `modifier`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `character_spell` (
|
||||
`character` integer PRIMARY KEY NOT NULL,
|
||||
`value` text NOT NULL,
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `character_training` (
|
||||
`character` integer NOT NULL,
|
||||
`stat` text NOT NULL,
|
||||
`level` integer NOT NULL,
|
||||
`choice` integer NOT NULL,
|
||||
PRIMARY KEY(`character`, `stat`, `level`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `people` integer NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `level` integer DEFAULT 1 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `aspect` integer;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `notes` text;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `health` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `mana` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `character` DROP COLUMN `progress`;--> statement-breakpoint
|
||||
ALTER TABLE `character` DROP COLUMN `values`;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `character_abilities` ADD `max` integer DEFAULT 0 NOT NULL;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
||||
CREATE TABLE `__new_character_spell` (
|
||||
`character` integer NOT NULL,
|
||||
`value` text NOT NULL,
|
||||
PRIMARY KEY(`character`, `value`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
INSERT INTO `__new_character_spell`("character", "value") SELECT "character", "value" FROM `character_spell`;--> statement-breakpoint
|
||||
DROP TABLE `character_spell`;--> statement-breakpoint
|
||||
ALTER TABLE `__new_character_spell` RENAME TO `character_spell`;--> statement-breakpoint
|
||||
PRAGMA foreign_keys=ON;
|
||||
|
|
@ -0,0 +1,426 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "eb68cf2f-c7e2-4111-910d-a26b0fc438cc",
|
||||
"prevId": "15ea15e0-3d44-4dff-a4cd-f8666c4aa5ed",
|
||||
"tables": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"progress": {
|
||||
"name": "progress",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"values": {
|
||||
"name": "values",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"thumbnail": {
|
||||
"name": "thumbnail",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_owner_users_id_fk": {
|
||||
"name": "character_owner_users_id_fk",
|
||||
"tableFrom": "character",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"email_validation": {
|
||||
"name": "email_validation",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"navigable": {
|
||||
"name": "navigable",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"private": {
|
||||
"name": "private",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_permissions": {
|
||||
"name": "user_permissions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"permission": {
|
||||
"name": "permission",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_permissions_id_users_id_fk": {
|
||||
"name": "user_permissions_id_users_id_fk",
|
||||
"tableFrom": "user_permissions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_permissions_id_permission_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"permission"
|
||||
],
|
||||
"name": "user_permissions_id_permission_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_sessions": {
|
||||
"name": "user_sessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_sessions_user_id_users_id_fk": {
|
||||
"name": "user_sessions_user_id_users_id_fk",
|
||||
"tableFrom": "user_sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_sessions_id_user_id_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"user_id"
|
||||
],
|
||||
"name": "user_sessions_id_user_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users_data": {
|
||||
"name": "users_data",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"signin": {
|
||||
"name": "signin",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"lastTimestamp": {
|
||||
"name": "lastTimestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_data_id_users_id_fk": {
|
||||
"name": "users_data_id_users_id_fk",
|
||||
"tableFrom": "users_data",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"hash": {
|
||||
"name": "hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"columns": [
|
||||
"username"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_hash_unique": {
|
||||
"name": "users_hash_unique",
|
||||
"columns": [
|
||||
"hash"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,434 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "bffde16c-d716-40ec-9d92-cb49814815d7",
|
||||
"prevId": "eb68cf2f-c7e2-4111-910d-a26b0fc438cc",
|
||||
"tables": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"progress": {
|
||||
"name": "progress",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"values": {
|
||||
"name": "values",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"visibility": {
|
||||
"name": "visibility",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'private'"
|
||||
},
|
||||
"thumbnail": {
|
||||
"name": "thumbnail",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_owner_users_id_fk": {
|
||||
"name": "character_owner_users_id_fk",
|
||||
"tableFrom": "character",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"email_validation": {
|
||||
"name": "email_validation",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"navigable": {
|
||||
"name": "navigable",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"private": {
|
||||
"name": "private",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_permissions": {
|
||||
"name": "user_permissions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"permission": {
|
||||
"name": "permission",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_permissions_id_users_id_fk": {
|
||||
"name": "user_permissions_id_users_id_fk",
|
||||
"tableFrom": "user_permissions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_permissions_id_permission_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"permission"
|
||||
],
|
||||
"name": "user_permissions_id_permission_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_sessions": {
|
||||
"name": "user_sessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_sessions_user_id_users_id_fk": {
|
||||
"name": "user_sessions_user_id_users_id_fk",
|
||||
"tableFrom": "user_sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_sessions_id_user_id_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"user_id"
|
||||
],
|
||||
"name": "user_sessions_id_user_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users_data": {
|
||||
"name": "users_data",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"signin": {
|
||||
"name": "signin",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"lastTimestamp": {
|
||||
"name": "lastTimestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_data_id_users_id_fk": {
|
||||
"name": "users_data_id_users_id_fk",
|
||||
"tableFrom": "users_data",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"hash": {
|
||||
"name": "hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"columns": [
|
||||
"username"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_hash_unique": {
|
||||
"name": "users_hash_unique",
|
||||
"columns": [
|
||||
"hash"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,724 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "af3d9e4f-cea6-42fa-8f8b-d743d97b9c37",
|
||||
"prevId": "bffde16c-d716-40ec-9d92-cb49814815d7",
|
||||
"tables": {
|
||||
"character_abilities": {
|
||||
"name": "character_abilities",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"ability": {
|
||||
"name": "ability",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_abilities_character_character_id_fk": {
|
||||
"name": "character_abilities_character_character_id_fk",
|
||||
"tableFrom": "character_abilities",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_abilities_character_ability_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"ability"
|
||||
],
|
||||
"name": "character_abilities_character_ability_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_leveling": {
|
||||
"name": "character_leveling",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"choice": {
|
||||
"name": "choice",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_leveling_character_character_id_fk": {
|
||||
"name": "character_leveling_character_character_id_fk",
|
||||
"tableFrom": "character_leveling",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_leveling_character_level_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"level"
|
||||
],
|
||||
"name": "character_leveling_character_level_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_modifiers": {
|
||||
"name": "character_modifiers",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"modifier": {
|
||||
"name": "modifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_modifiers_character_character_id_fk": {
|
||||
"name": "character_modifiers_character_character_id_fk",
|
||||
"tableFrom": "character_modifiers",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_modifiers_character_modifier_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"modifier"
|
||||
],
|
||||
"name": "character_modifiers_character_modifier_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_spell": {
|
||||
"name": "character_spell",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_spell_character_character_id_fk": {
|
||||
"name": "character_spell_character_character_id_fk",
|
||||
"tableFrom": "character_spell",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character": {
|
||||
"name": "character",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"people": {
|
||||
"name": "people",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 1
|
||||
},
|
||||
"aspect": {
|
||||
"name": "aspect",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"health": {
|
||||
"name": "health",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"mana": {
|
||||
"name": "mana",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"visibility": {
|
||||
"name": "visibility",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'private'"
|
||||
},
|
||||
"thumbnail": {
|
||||
"name": "thumbnail",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_owner_users_id_fk": {
|
||||
"name": "character_owner_users_id_fk",
|
||||
"tableFrom": "character",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_training": {
|
||||
"name": "character_training",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"stat": {
|
||||
"name": "stat",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"choice": {
|
||||
"name": "choice",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_training_character_character_id_fk": {
|
||||
"name": "character_training_character_character_id_fk",
|
||||
"tableFrom": "character_training",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_training_character_stat_level_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"stat",
|
||||
"level"
|
||||
],
|
||||
"name": "character_training_character_stat_level_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"email_validation": {
|
||||
"name": "email_validation",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"navigable": {
|
||||
"name": "navigable",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"private": {
|
||||
"name": "private",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_permissions": {
|
||||
"name": "user_permissions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"permission": {
|
||||
"name": "permission",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_permissions_id_users_id_fk": {
|
||||
"name": "user_permissions_id_users_id_fk",
|
||||
"tableFrom": "user_permissions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_permissions_id_permission_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"permission"
|
||||
],
|
||||
"name": "user_permissions_id_permission_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_sessions": {
|
||||
"name": "user_sessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_sessions_user_id_users_id_fk": {
|
||||
"name": "user_sessions_user_id_users_id_fk",
|
||||
"tableFrom": "user_sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_sessions_id_user_id_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"user_id"
|
||||
],
|
||||
"name": "user_sessions_id_user_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users_data": {
|
||||
"name": "users_data",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"signin": {
|
||||
"name": "signin",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"lastTimestamp": {
|
||||
"name": "lastTimestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_data_id_users_id_fk": {
|
||||
"name": "users_data_id_users_id_fk",
|
||||
"tableFrom": "users_data",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"hash": {
|
||||
"name": "hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"columns": [
|
||||
"username"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_hash_unique": {
|
||||
"name": "users_hash_unique",
|
||||
"columns": [
|
||||
"hash"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,732 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "e0aaebf1-54e4-4f61-804b-7cce23c88069",
|
||||
"prevId": "af3d9e4f-cea6-42fa-8f8b-d743d97b9c37",
|
||||
"tables": {
|
||||
"character_abilities": {
|
||||
"name": "character_abilities",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"ability": {
|
||||
"name": "ability",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"max": {
|
||||
"name": "max",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_abilities_character_character_id_fk": {
|
||||
"name": "character_abilities_character_character_id_fk",
|
||||
"tableFrom": "character_abilities",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_abilities_character_ability_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"ability"
|
||||
],
|
||||
"name": "character_abilities_character_ability_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_leveling": {
|
||||
"name": "character_leveling",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"choice": {
|
||||
"name": "choice",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_leveling_character_character_id_fk": {
|
||||
"name": "character_leveling_character_character_id_fk",
|
||||
"tableFrom": "character_leveling",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_leveling_character_level_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"level"
|
||||
],
|
||||
"name": "character_leveling_character_level_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_modifiers": {
|
||||
"name": "character_modifiers",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"modifier": {
|
||||
"name": "modifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_modifiers_character_character_id_fk": {
|
||||
"name": "character_modifiers_character_character_id_fk",
|
||||
"tableFrom": "character_modifiers",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_modifiers_character_modifier_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"modifier"
|
||||
],
|
||||
"name": "character_modifiers_character_modifier_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_spell": {
|
||||
"name": "character_spell",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_spell_character_character_id_fk": {
|
||||
"name": "character_spell_character_character_id_fk",
|
||||
"tableFrom": "character_spell",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character": {
|
||||
"name": "character",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"people": {
|
||||
"name": "people",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 1
|
||||
},
|
||||
"aspect": {
|
||||
"name": "aspect",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"health": {
|
||||
"name": "health",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"mana": {
|
||||
"name": "mana",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"visibility": {
|
||||
"name": "visibility",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'private'"
|
||||
},
|
||||
"thumbnail": {
|
||||
"name": "thumbnail",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_owner_users_id_fk": {
|
||||
"name": "character_owner_users_id_fk",
|
||||
"tableFrom": "character",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_training": {
|
||||
"name": "character_training",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"stat": {
|
||||
"name": "stat",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"choice": {
|
||||
"name": "choice",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_training_character_character_id_fk": {
|
||||
"name": "character_training_character_character_id_fk",
|
||||
"tableFrom": "character_training",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_training_character_stat_level_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"stat",
|
||||
"level"
|
||||
],
|
||||
"name": "character_training_character_stat_level_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"email_validation": {
|
||||
"name": "email_validation",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"navigable": {
|
||||
"name": "navigable",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"private": {
|
||||
"name": "private",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_permissions": {
|
||||
"name": "user_permissions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"permission": {
|
||||
"name": "permission",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_permissions_id_users_id_fk": {
|
||||
"name": "user_permissions_id_users_id_fk",
|
||||
"tableFrom": "user_permissions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_permissions_id_permission_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"permission"
|
||||
],
|
||||
"name": "user_permissions_id_permission_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_sessions": {
|
||||
"name": "user_sessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_sessions_user_id_users_id_fk": {
|
||||
"name": "user_sessions_user_id_users_id_fk",
|
||||
"tableFrom": "user_sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_sessions_id_user_id_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"user_id"
|
||||
],
|
||||
"name": "user_sessions_id_user_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users_data": {
|
||||
"name": "users_data",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"signin": {
|
||||
"name": "signin",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"lastTimestamp": {
|
||||
"name": "lastTimestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_data_id_users_id_fk": {
|
||||
"name": "users_data_id_users_id_fk",
|
||||
"tableFrom": "users_data",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"hash": {
|
||||
"name": "hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"columns": [
|
||||
"username"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_hash_unique": {
|
||||
"name": "users_hash_unique",
|
||||
"columns": [
|
||||
"hash"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,740 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "cb7a2b9c-1392-4f23-9fc2-9ce8de2e0231",
|
||||
"prevId": "e0aaebf1-54e4-4f61-804b-7cce23c88069",
|
||||
"tables": {
|
||||
"character_abilities": {
|
||||
"name": "character_abilities",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"ability": {
|
||||
"name": "ability",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"max": {
|
||||
"name": "max",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_abilities_character_character_id_fk": {
|
||||
"name": "character_abilities_character_character_id_fk",
|
||||
"tableFrom": "character_abilities",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_abilities_character_ability_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"ability"
|
||||
],
|
||||
"name": "character_abilities_character_ability_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_leveling": {
|
||||
"name": "character_leveling",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"choice": {
|
||||
"name": "choice",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_leveling_character_character_id_fk": {
|
||||
"name": "character_leveling_character_character_id_fk",
|
||||
"tableFrom": "character_leveling",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_leveling_character_level_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"level"
|
||||
],
|
||||
"name": "character_leveling_character_level_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_modifiers": {
|
||||
"name": "character_modifiers",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"modifier": {
|
||||
"name": "modifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_modifiers_character_character_id_fk": {
|
||||
"name": "character_modifiers_character_character_id_fk",
|
||||
"tableFrom": "character_modifiers",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_modifiers_character_modifier_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"modifier"
|
||||
],
|
||||
"name": "character_modifiers_character_modifier_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_spell": {
|
||||
"name": "character_spell",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_spell_character_character_id_fk": {
|
||||
"name": "character_spell_character_character_id_fk",
|
||||
"tableFrom": "character_spell",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_spell_character_value_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"value"
|
||||
],
|
||||
"name": "character_spell_character_value_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character": {
|
||||
"name": "character",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"people": {
|
||||
"name": "people",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 1
|
||||
},
|
||||
"aspect": {
|
||||
"name": "aspect",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"health": {
|
||||
"name": "health",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"mana": {
|
||||
"name": "mana",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"visibility": {
|
||||
"name": "visibility",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'private'"
|
||||
},
|
||||
"thumbnail": {
|
||||
"name": "thumbnail",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_owner_users_id_fk": {
|
||||
"name": "character_owner_users_id_fk",
|
||||
"tableFrom": "character",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_training": {
|
||||
"name": "character_training",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"stat": {
|
||||
"name": "stat",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"choice": {
|
||||
"name": "choice",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_training_character_character_id_fk": {
|
||||
"name": "character_training_character_character_id_fk",
|
||||
"tableFrom": "character_training",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_training_character_stat_level_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"stat",
|
||||
"level"
|
||||
],
|
||||
"name": "character_training_character_stat_level_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"email_validation": {
|
||||
"name": "email_validation",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"navigable": {
|
||||
"name": "navigable",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"private": {
|
||||
"name": "private",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_permissions": {
|
||||
"name": "user_permissions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"permission": {
|
||||
"name": "permission",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_permissions_id_users_id_fk": {
|
||||
"name": "user_permissions_id_users_id_fk",
|
||||
"tableFrom": "user_permissions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_permissions_id_permission_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"permission"
|
||||
],
|
||||
"name": "user_permissions_id_permission_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_sessions": {
|
||||
"name": "user_sessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_sessions_user_id_users_id_fk": {
|
||||
"name": "user_sessions_user_id_users_id_fk",
|
||||
"tableFrom": "user_sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_sessions_id_user_id_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"user_id"
|
||||
],
|
||||
"name": "user_sessions_id_user_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users_data": {
|
||||
"name": "users_data",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"signin": {
|
||||
"name": "signin",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"lastTimestamp": {
|
||||
"name": "lastTimestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_data_id_users_id_fk": {
|
||||
"name": "users_data_id_users_id_fk",
|
||||
"tableFrom": "users_data",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"hash": {
|
||||
"name": "hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"columns": [
|
||||
"username"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_hash_unique": {
|
||||
"name": "users_hash_unique",
|
||||
"columns": [
|
||||
"hash"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,41 @@
|
|||
"when": 1745074613379,
|
||||
"tag": "0007_tearful_true_believers",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "6",
|
||||
"when": 1745675022171,
|
||||
"tag": "0008_glorious_johnny_blaze",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "6",
|
||||
"when": 1745920443528,
|
||||
"tag": "0009_thin_omega_sentinel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "6",
|
||||
"when": 1746014143374,
|
||||
"tag": "0010_bored_sabra",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "6",
|
||||
"when": 1746017162319,
|
||||
"tag": "0011_demonic_titania",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "6",
|
||||
"when": 1746027790969,
|
||||
"tag": "0012_graceful_energizer",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -14,10 +14,21 @@
|
|||
<span class="text-xl max-md:hidden">d[any]</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="flex items-center gap-8 max-md:hidden">
|
||||
<Tooltip message="Developpement en cours" side="bottom"><NuxtLink href="#" class="text-light-70 dark:text-dark-70">Parcourir les projets</NuxtLink></Tooltip>
|
||||
<Tooltip message="Developpement en cours" side="bottom"><NuxtLink href="#" class="text-light-70 dark:text-dark-70">Créer du contenu</NuxtLink></Tooltip>
|
||||
</div>
|
||||
<NavigationMenuRoot class="relative">
|
||||
<NavigationMenuList class="flex items-center gap-8 max-md:hidden">
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>
|
||||
<NuxtLink :href="{ name: 'character' }" class="text-light-70 dark:text-dark-70" active-class="!text-accent-blue"><span class="pl-3 py-1 flex-1 truncate">Personnages</span></NuxtLink>
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent class="absolute top-0 left-0 w-full sm:w-auto bg-light-0 dark:bg-dark-0 border border-light-30 dark:border-dark-30">
|
||||
<NuxtLink :href="{ name: 'character-list' }" class="text-light-70 dark:text-dark-70" active-class="!text-accent-blue"><span class="py-2 px-3 flex-1 truncate">Tous les personnages</span></NuxtLink>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
<div class="absolute top-full left-0 flex w-full justify-center my-4">
|
||||
<NavigationMenuViewport class="h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] overflow-hidden rounded-[10px] bg-white transition-[width,_height] duration-300 sm:w-[var(--radix-navigation-menu-viewport-width)]" />
|
||||
</div>
|
||||
</NavigationMenuRoot>
|
||||
<div class="flex items-center px-2 gap-4">
|
||||
<template v-if="!loggedIn">
|
||||
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-light-70 dark:hover:text-dark-70" :to="{ name: 'user-login' }">Se connecter</NuxtLink>
|
||||
|
|
@ -33,9 +44,6 @@
|
|||
<div class="bg-light-0 dark:bg-dark-0 z-40 w-screen md:w-[18rem] border-r border-light-30 dark:border-dark-30 flex flex-col justify-between my-2 max-md:data-[state=closed]:hidden">
|
||||
<div class="flex-1 px-2 max-w-full max-h-full overflow-y-auto overflow-x-hidden">
|
||||
<div v-if="user" class="flex flex-1 py-4 px-2 flex-row flex-1 justify-between items-center">
|
||||
<NuxtLink :href="{ name: 'character' }" class="flex flex-1 font-bold text-lg items-center border-light-35 dark:border-dark-35 hover:border-accent-blue" active-class="text-accent-blue border-s-2 !border-accent-blue">
|
||||
<span class="pl-3 py-1 flex-1 truncate">Mes personnages</span>
|
||||
</NuxtLink>
|
||||
<NuxtLink v-if="hasPermissions(user.permissions, ['admin', 'editor'])" :to="{ name: 'explore-edit' }"><Button icon><Icon icon="radix-icons:pencil-2" /></Button></NuxtLink>
|
||||
</div>
|
||||
<Tree v-if="pages" v-model="pages" :getKey="(item) => item.path" class="ps-4">
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ export default defineNuxtConfig({
|
|||
mail: {
|
||||
host: '',
|
||||
port: '',
|
||||
proxy: '',
|
||||
user: '',
|
||||
passwd: '',
|
||||
dkim: '',
|
||||
|
|
@ -161,6 +162,7 @@ export default defineNuxtConfig({
|
|||
sources: ['/api/__sitemap__/urls']
|
||||
},
|
||||
experimental: {
|
||||
buildCache: true,
|
||||
componentIslands: {
|
||||
selectiveClient: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
<script lang="ts">
|
||||
import config from '#shared/character-config.json';
|
||||
|
||||
function raceOptionToText(option: RaceOption): string
|
||||
{
|
||||
const text = [];
|
||||
if(option.training) text.push(`+${option.training} point${option.training > 1 ? 's' : ''} de statistique${option.training > 1 ? 's' : ''}.`);
|
||||
if(option.spec) text.push(`+${option.spec} spécialisation${option.spec > 1 ? 's' : ''}.`);
|
||||
if(option.shaping) text.push(`+${option.shaping} transformation${option.shaping > 1 ? 's' : ''} par jour.`);
|
||||
if(option.modifier) text.push(`+${option.modifier} au modifieur de votre choix.`);
|
||||
if(option.abilities) text.push(`+${option.abilities} point${option.abilities > 1 ? 's' : ''} de compétence${option.abilities > 1 ? 's' : ''}.`);
|
||||
if(option.health) text.push(`+${option.health} PV max.`);
|
||||
if(option.mana) text.push(`+${option.mana} mana max.`);
|
||||
if(option.spellslots) text.push(`+${option.spellslots} sort${option.spellslots > 1 ? 's' : ''} maitrisé${option.spellslots > 1 ? 's' : ''}.`);
|
||||
return text.join('\n');
|
||||
}
|
||||
function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]): TrainingOption[]
|
||||
|
|
@ -17,23 +19,15 @@ function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]
|
|||
return progression.map(e => characterData.training[stat][e[0]][e[1]]);
|
||||
}
|
||||
|
||||
const mainStatTexts: Record<MainStat, string> = {
|
||||
"strength": "Force",
|
||||
"dexterity": "Dextérité",
|
||||
"constitution": "Constitution",
|
||||
"intelligence": "Intelligence",
|
||||
"curiosity": "Curiosité",
|
||||
"charisma": "Charisme",
|
||||
"psyche": "Psyché",
|
||||
}
|
||||
|
||||
|
||||
function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<TrainingLevel>[], value: number): number
|
||||
{
|
||||
if(type === 'points')
|
||||
{
|
||||
if(curiosity.find(e => e[0] == 7 && e[1] === 0))
|
||||
if(curiosity.find(e => e[0] == 6 && e[1] === 0))
|
||||
return Math.max(6, value);
|
||||
if(curiosity.find(e => e[0] == 7 && e[1] === 2))
|
||||
if(curiosity.find(e => e[0] == 6 && e[1] === 2))
|
||||
return value + 1;
|
||||
}
|
||||
return value;
|
||||
|
|
@ -43,7 +37,7 @@ function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<T
|
|||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import PreviewA from '~/components/prose/PreviewA.vue';
|
||||
import { clamp } from '~/shared/general.util';
|
||||
import type { Ability, Character, CharacterConfig, DoubleIndex, Level, MainStat, RaceOption, TrainingLevel, TrainingOption } from '~/types/character';
|
||||
import { defaultCharacter, elementTexts, mainStatTexts, spellTypeTexts, type Ability, type Character, type CharacterConfig, type DoubleIndex, type Level, type MainStat, type RaceOption, type SpellConfig, type SpellElement, type SpellType, type TrainingLevel, type TrainingOption } from '~/types/character';
|
||||
|
||||
definePageMeta({
|
||||
guestsGoesTo: '/user/login',
|
||||
|
|
@ -51,43 +45,36 @@ definePageMeta({
|
|||
let id = useRouter().currentRoute.value.params.id;
|
||||
const { add } = useToast();
|
||||
const characterConfig = config as CharacterConfig;
|
||||
const data = ref<Character>({
|
||||
id: -1,
|
||||
name: '',
|
||||
progress: {
|
||||
training: {
|
||||
strength: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
dexterity: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
constitution: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
intelligence: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
curiosity: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
charisma: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
psyche: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
},
|
||||
level: 1,
|
||||
race: {
|
||||
index: undefined,
|
||||
progress: [[1, 0]],
|
||||
},
|
||||
abilities: {},
|
||||
modifiers: {},
|
||||
},
|
||||
const data = ref<Character>({ ...defaultCharacter });
|
||||
const spellFilter = ref<{
|
||||
ranks: Array<1 | 2 | 3>,
|
||||
types: Array<SpellType>,
|
||||
text: string,
|
||||
elements: Array<SpellElement>,
|
||||
tags: string[],
|
||||
}>({
|
||||
ranks: [],
|
||||
types: [],
|
||||
text: "",
|
||||
elements: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const peopleOpen = ref(false), trainingOpen = ref(false), abilityOpen = ref(false), trainingTab = ref(0);
|
||||
const trainingPoints = computed(() => {
|
||||
const options = data.value.progress.race.index !== undefined ? characterConfig.peoples[data.value.progress.race.index!].options : undefined;
|
||||
return options ? data.value.progress.race.progress?.reduce((p, v) => {
|
||||
return p + (options[v[0]][v[1]].training ?? 0)
|
||||
}, 0) : 0;
|
||||
});
|
||||
const training = computed(() => Object.entries(characterConfig.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, data.value.progress.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][])
|
||||
const maxTraining = computed(() => Object.entries(data.value.progress.training).reduce((p, v) => { p[v[0]] = v[1].reduce((_p, _v) => Math.max(_p, _v[0]) , 0); return p; }, {} as Record<MainStat, number>));
|
||||
const peopleOpen = ref(false), trainingOpen = ref(false), abilityOpen = ref(false), spellOpen = ref(false), notesOpen = ref(false), trainingTab = ref(0);
|
||||
const raceOptions = computed(() => data.value.people !== undefined ? characterConfig.peoples[data.value.people!].options : undefined);
|
||||
const selectedRaceOptions = computed(() => raceOptions !== undefined ? data.value.leveling!.map(e => raceOptions.value![e[0]][e[1]]) : undefined);
|
||||
const trainingPoints = computed(() => raceOptions.value ? data.value.leveling?.reduce((p, v) => p + (raceOptions.value![v[0]][v[1]].training ?? 0), 0) : 0);
|
||||
const training = computed(() => Object.entries(characterConfig.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, data.value.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][]);
|
||||
const maxTraining = computed(() => Object.entries(data.value.training).reduce((p, v) => { p[v[0] as MainStat] = v[1].reduce((_p, _v) => Math.max(_p, _v[0]) , 0); return p; }, {} as Record<MainStat, number>));
|
||||
const trainingSpent = computed(() => Object.values(maxTraining.value).reduce((p, v) => p + v, 0));
|
||||
const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0]] = Math.floor(v[1] / 3) + (data.value.progress.modifiers ? (data.value.progress.modifiers[v[0] as MainStat] ?? 0) : 0); return p; }, {} as Record<MainStat, number>))
|
||||
const abilityPoints = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.ability !== undefined)).reduce((p, v) => p + v.ability!, 0));
|
||||
const abilityMax = computed(() => Object.entries(characterConfig.ability).reduce((p, v) => { p[v[0]] = abilitySpecialFeatures("max", data.value.progress.training.curiosity, Math.floor(maxTraining.value[v[1].max[0]] / 3) + Math.floor(maxTraining.value[v[1].max[1]] / 3)); return p; }, {} as Record<Ability, number>));
|
||||
const abilitySpent = computed(() => Object.values(data.value.progress.abilities ?? {}).reduce((p, v) => p + v[0], 0));
|
||||
const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0] as MainStat] = Math.floor(v[1] / 3) + (data.value.modifiers ? (data.value.modifiers[v[0] as MainStat] ?? 0) : 0); return p; }, {} as Record<MainStat, number>))
|
||||
const modifierPoints = computed(() => (selectedRaceOptions.value ? selectedRaceOptions.value.reduce((p, v) => p + (v?.modifier ?? 0), 0) : 0) + training.value.reduce((p, v) => p + v[1].reduce((_p, _v) => _p + (_v?.modifier ?? 0), 0), 0));
|
||||
const modifierSpent = computed(() => Object.values(data.value.modifiers ?? {}).reduce((p, v) => p + v, 0));
|
||||
const abilityPoints = computed(() => (selectedRaceOptions.value ? selectedRaceOptions.value.reduce((p, v) => p + (v?.abilities ?? 0), 0) : 0) + training.value.flatMap(e => e[1].filter(_e => _e.ability !== undefined)).reduce((p, v) => p + v.ability!, 0));
|
||||
const abilityMax = computed(() => Object.entries(characterConfig.abilities).reduce((p, v) => { p[v[0] as Ability] = abilitySpecialFeatures("max", data.value.training.curiosity, Math.floor(maxTraining.value[v[1].max[0]] / 3) + Math.floor(maxTraining.value[v[1].max[1]] / 3)); return p; }, {} as Record<Ability, number>));
|
||||
const abilitySpent = computed(() => Object.values(data.value.abilities ?? {}).reduce((p, v) => p + v[0], 0));
|
||||
const spellranks = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.spellrank !== undefined)).reduce((p, v) => { p[v.spellrank!]++; return p; }, { instinct: 0, precision: 0, knowledge: 0 } as Record<SpellType, 0 | 1 | 2 | 3>));
|
||||
const spellsPoints = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.spellslot !== undefined)).reduce((p, v) => p + (modifiers.value.hasOwnProperty(v.spellslot as MainStat) ? modifiers.value[v.spellslot as MainStat] : v.spellslot as number), 0));
|
||||
|
||||
if(id !== 'new')
|
||||
{
|
||||
|
|
@ -98,31 +85,34 @@ if(id !== 'new')
|
|||
throw new Error('Donnée du personnage introuvables');
|
||||
}
|
||||
|
||||
data.value = { name: character.name, progress: character.progress } as Character;
|
||||
data.value = Object.assign(defaultCharacter, data.value, character);
|
||||
}
|
||||
|
||||
function selectRaceOption(level: Level, choice: number)
|
||||
{
|
||||
const character = data.value;
|
||||
if(level > character.progress.level)
|
||||
if(level > character.level)
|
||||
return;
|
||||
|
||||
if(character.progress.race.progress === undefined)
|
||||
character.progress.race.progress = [];
|
||||
if(character.leveling === undefined)
|
||||
character.leveling = [[1, 0]];
|
||||
|
||||
if(level == 1)
|
||||
return;
|
||||
|
||||
for(let i = 1; i < level; i++) //Check previous levels as a requirement
|
||||
{
|
||||
if(!character.progress.race.progress.some(e => e[0] == i))
|
||||
if(!character.leveling.some(e => e[0] == i))
|
||||
return;
|
||||
}
|
||||
|
||||
if(character.progress.race.progress.some(e => e[0] === level))
|
||||
if(character.leveling.some(e => e[0] == level))
|
||||
{
|
||||
character.progress.race.progress.splice(character.progress.race.progress.findIndex(e => e[0] === level), 1, [level, choice]);
|
||||
character.leveling.splice(character.leveling.findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.progress.race.progress.push([level, choice]);
|
||||
character.leveling.push([level, choice]);
|
||||
}
|
||||
|
||||
data.value = character;
|
||||
|
|
@ -136,27 +126,27 @@ function switchTrainingOption(stat: MainStat, level: TrainingLevel, choice: numb
|
|||
|
||||
for(let i = 1; i < level; i++) //Check previous levels as a requirement
|
||||
{
|
||||
if(!character.progress.training[stat].some(e => e[0] == i))
|
||||
if(!character.training[stat].some(e => e[0] == i))
|
||||
return;
|
||||
}
|
||||
|
||||
if(character.progress.training[stat].some(e => e[0] === level))
|
||||
if(character.training[stat].some(e => e[0] == level))
|
||||
{
|
||||
if(character.progress.training[stat].some(e => e[0] === level && e[1] === choice))
|
||||
if(character.training[stat].some(e => e[0] == level && e[1] === choice))
|
||||
{
|
||||
for(let i = 15; i >= level; i --) //Invalidate higher levels
|
||||
{
|
||||
const index = character.progress.training[stat].findIndex(e => e[0] == i);
|
||||
const index = character.training[stat].findIndex(e => e[0] == i);
|
||||
if(index !== -1)
|
||||
character.progress.training[stat].splice(index, 1);
|
||||
character.training[stat].splice(index, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
character.progress.training[stat].splice(character.progress.training[stat].findIndex(e => e[0] === level), 1, [level, choice]);
|
||||
character.training[stat].splice(character.training[stat].findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
}
|
||||
else if(trainingPoints.value && trainingPoints.value > 0)
|
||||
{
|
||||
character.progress.training[stat].push([level, choice]);
|
||||
character.training[stat].push([level, choice]);
|
||||
}
|
||||
|
||||
data.value = character;
|
||||
|
|
@ -165,21 +155,34 @@ function updateLevel()
|
|||
{
|
||||
const character = data.value;
|
||||
|
||||
if(character.progress.race.progress) //Invalidate higher levels
|
||||
if(character.leveling) //Invalidate higher levels
|
||||
{
|
||||
for(let level = 20; level > character.progress.level; level--)
|
||||
for(let level = 20; level > character.level; level--)
|
||||
{
|
||||
const index = character.progress.race.progress.findIndex(e => e[0] == level);
|
||||
const index = character.leveling.findIndex(e => e[0] == level);
|
||||
if(index !== -1)
|
||||
character.progress.race.progress.splice(index, 1);
|
||||
character.leveling.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
data.value = character;
|
||||
}
|
||||
function filterSpells(spells: SpellConfig[])
|
||||
{
|
||||
const filter = spellFilter.value
|
||||
let list = [...spells];
|
||||
list = list.filter(e => spellranks.value[e.type] >= e.rank);
|
||||
if(filter.text.length > 0) list = list.filter(e => e.name.toLowerCase().includes(filter.text.toLowerCase()));
|
||||
if(filter.types.length > 0) list = list.filter(e => filter.types.includes(e.type));
|
||||
if(filter.ranks.length > 0) list = list.filter(e => filter.ranks.includes(e.rank));
|
||||
if(filter.elements.length > 0) list = list.filter(e => filter.elements.some(f => e.elements.includes(f)));
|
||||
if(filter.tags.length > 0) list = list.filter(e => !e.tags || filter.tags.some(f => e.tags!.includes(f)));
|
||||
|
||||
return list;
|
||||
}
|
||||
async function save(leave: boolean)
|
||||
{
|
||||
if(data.value.name === '' || data.value.progress.race.index === undefined || data.value.progress.race.index === -1)
|
||||
if(data.value.name === '' || data.value.people === undefined || data.value.people === -1)
|
||||
{
|
||||
add({ title: 'Données manquantes', content: "Merci de saisir un nom et une race avant de pouvoir enregistrer votre personnage", type: 'error', duration: 25000, timer: true });
|
||||
return;
|
||||
|
|
@ -190,7 +193,7 @@ async function save(leave: boolean)
|
|||
method: 'post',
|
||||
body: data.value,
|
||||
onResponseError: (e) => {
|
||||
add({ title: 'Erreur d\enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
}
|
||||
});
|
||||
add({ content: 'Personnage créé', type: 'success', duration: 25000, timer: true });
|
||||
|
|
@ -203,7 +206,7 @@ async function save(leave: boolean)
|
|||
method: 'post',
|
||||
body: data.value,
|
||||
onResponseError: (e) => {
|
||||
add({ title: 'Erreur d\enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
}
|
||||
});
|
||||
add({ content: 'Personnage enregistré', type: 'success', duration: 25000, timer: true });
|
||||
|
|
@ -234,63 +237,77 @@ useShortcuts({
|
|||
</Label>
|
||||
<Label class="flex items-start justify-between flex-col gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Niveau</span>
|
||||
<NumberFieldRoot :min="1" :max="20" v-model="data.progress.level" @update:model-value="updateLevel" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
<NumberFieldRoot :min="1" :max="20" v-model="data.level" @update:model-value="updateLevel" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
|
||||
<NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Label class="flex items-start justify-between flex-col gap-2">
|
||||
<span class="pb-1 mx-6 md:p-0">Visibilité</span>
|
||||
<Select class="!my-0" v-model="data.visibility">
|
||||
<SelectItem label="Privé" value="private" />
|
||||
<SelectItem label="Public" value="public" />
|
||||
</Select>
|
||||
</Label>
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<Tooltip side="right" message="Ctrl+S"><Button @click="() => save(true)">Enregistrer</Button></Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col min-w-[800px] w-[75vw] max-w-[1200px]">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="peopleOpen" @update:model-value="() => { trainingOpen = false; abilityOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="peopleOpen" @update:model-value="() => { trainingOpen = false; abilityOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Peuple</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="m-2 overflow-auto">
|
||||
<Select label="Peuple de votre personnage" :v-model="data.progress.race.index" @update:model-value="(index) => { data.progress.race.index = parseInt(index ?? '-1'); data.progress.race.progress = [[1, 0]]}">
|
||||
<SelectItem v-for="(people, index) of characterConfig.peoples" :label="people.name" :value="index" :key="index" />
|
||||
</Select>
|
||||
<template v-if="data.progress.race.index !== undefined">
|
||||
<Combobox label="Peuple de votre personnage" v-model="data.people" :options="config.peoples.map((people, index) => [people.name, index])" @update:model-value="(index) => { data.people = index as number | undefined; data.leveling = [[1, 0]]}" />
|
||||
<template v-if="data.people !== undefined">
|
||||
<div class="w-full border-b border-light-30 dark:border-dark-30 pb-4">
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.progress.race.index].description }}</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.people].description }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 max-h-[50vh] pe-4 relative">
|
||||
<span class="sticky top-0 py-1 bg-light-0 dark:bg-dark-0 z-10 text-xl">Niveaux restants: {{ data.progress.level - (data.progress.race.progress?.length ?? 0) }}</span>
|
||||
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.peoples[data.progress.race.index].options" :class="{ 'opacity-30': index > data.progress.level }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-64" v-for="(option, i) of level" @click="selectRaceOption(index as Level, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= data.progress.level, '!border-accent-blue bg-accent-blue bg-opacity-20': data.progress.race.progress?.some(e => e[0] == index && e[1] === i) ?? false }"><MarkdownRenderer :content="raceOptionToText(option)" /></div>
|
||||
<span class="sticky top-0 py-1 bg-light-0 dark:bg-dark-0 z-10 text-xl">Niveaux restants: {{ data.level - (data.leveling?.length ?? 0) }}</span>
|
||||
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.peoples[data.people].options" :class="{ 'opacity-30': index > data.level }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-64" v-for="(option, i) of level" @click="selectRaceOption(parseInt(index as unknown as string, 10) as Level, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= data.level, '!border-accent-blue bg-accent-blue bg-opacity-20': data.leveling?.some(e => e[0] == index && e[1] === i) ?? false }"><MarkdownRenderer :content="raceOptionToText(option)" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="trainingOpen" :disabled="data.progress.race.index === undefined" @update:model-value="() => { peopleOpen = false; abilityOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="trainingOpen" :disabled="data.people === undefined" @update:model-value="() => { peopleOpen = false; abilityOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Entrainement</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-4 max-h-[50vh] pe-4 relative overflow-y-auto overflow-x-hidden">
|
||||
<div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex justify-between">
|
||||
<div class="sticky top-0 z-10 py-2 bg-light-0 dark:bg-dark-0 flex justify-between">
|
||||
<Icon icon="radix-icons:caret-left" class="w-6 h-6 border border-light-30 dark:border-dark-30 cursor-pointer" @click="() => trainingTab = clamp(trainingTab - 1, 0, 6)" />
|
||||
<span class="text-xl" :class="{ 'text-light-red dark:text-dark-red': (trainingPoints ?? 0) < trainingSpent }">Points d'entrainement restants: {{ (trainingPoints ?? 0) - trainingSpent }}</span>
|
||||
<Icon icon="radix-icons:caret-right" class="w-6 h-6 border border-light-30 dark:border-dark-30 cursor-pointer" @click="() => trainingTab = clamp(trainingTab + 1, 0, 6)" />
|
||||
</div>
|
||||
<div class="flex gap-4 relative" :style="`left: calc(calc(-100% - 1em) * ${trainingTab}); transition: left .5s ease;`">
|
||||
<div class="flex w-full flex-shrink-0 flex-col gap-2 relative" v-for="(text, stat) of mainStatTexts">
|
||||
<div class="sticky top-1 left-16 py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold self-start border border-light-30 dark:border-dark-30">{{ text }}<span v-if="maxTraining[stat] >= 0">: Niveau {{ maxTraining[stat] }} (+{{ modifiers[stat] }})</span></div>
|
||||
<div class="sticky top-1 mx-16 z-10 flex justify-between">
|
||||
<div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold border border-light-30 dark:border-dark-30 flex">{{ text }}
|
||||
<div class="flex gap-2" v-if="maxTraining[stat] >= 0">: Niveau {{ maxTraining[stat] }} (+{{ modifiers[stat] }}
|
||||
<NumberFieldRoot :default-value="data.modifiers[stat] ?? 0" v-model="data.modifiers[stat]" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
|
||||
<NumberFieldInput class="tabular-nums w-8 text-base font-normal bg-transparent px-2 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
)</div></div>
|
||||
<div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 flex gap-2 justify-center items-center" :class="{ 'text-light-red dark:text-dark-red': (modifierPoints ?? 0) < modifierSpent }">Modifieur bonus: {{ modifierPoints - modifierSpent }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training[stat]" :class="{ 'opacity-30': index > maxTraining[stat] + 1 }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption(stat, index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining[stat] + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training[stat]?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption(stat, parseInt(index as unknown as string, 10) as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining[stat] + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.training[stat]?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="abilityOpen" :disabled="data.progress.race.index === undefined" @update:model-value="() => { trainingOpen = false; peopleOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="abilityOpen" :disabled="data.people === undefined" @update:model-value="() => { trainingOpen = false; peopleOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Compétences</span>
|
||||
</template>
|
||||
|
|
@ -299,18 +316,66 @@ useShortcuts({
|
|||
<div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex justify-between">
|
||||
<span class="text-xl -mx-2" :class="{ 'text-light-red dark:text-dark-red': (abilityPoints ?? 0) < abilitySpent }">Points d'entrainement restants: {{ (abilityPoints ?? 0) - abilitySpent }}</span>
|
||||
</div>
|
||||
<div v-for="(ability, index) of characterConfig.ability" class="grid grid-cols-10 gap-4 items-center">
|
||||
<span class="text-lg col-span-2">{{ ability.name }}</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70 col-span-2">({{ mainStatTexts[ability.max[0]] }} + {{ mainStatTexts[ability.max[1]] }})</span>
|
||||
<NumberFieldRoot :min="0" :max="abilityMax[index]" :default-value="data.progress.abilities[index] ? data.progress.abilities[index][0] : 0" @update:model-value="(value) => { data.progress.abilities[index] = [value, data.progress.abilities[index] ? data.progress.abilities[index][1] : 0]; console.log(abilitySpent) }" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
|
||||
<NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
<span class="font-bold col-span-4">(max: {{ abilityMax[index] }})</span>
|
||||
<div class="grid gap-4 grid-cols-6">
|
||||
<div v-for="(ability, index) of characterConfig.abilities" class="flex flex-col items-center border border-light-30 dark:border-dark-30 p-2">
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<NumberFieldRoot :min="0" :default-value="data.abilities[index] ? data.abilities[index][0] : 0" @update:model-value="(value) => { data.abilities[index] = [value, data.abilities[index] ? data.abilities[index][1] : 0]; }" class="border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
|
||||
<NumberFieldInput class="tabular-nums w-8 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
<span class="font-bold col-span-4">/{{ abilityMax[index] }}</span>
|
||||
</div>
|
||||
<span class="text-xl font-bold flex-2">{{ ability.name }}</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70 flex-1">({{ mainStatTexts[ability.max[0]] }} + {{ mainStatTexts[ability.max[1]] }})</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="spellOpen" :disabled="data.people === undefined" @update:model-value="() => { trainingOpen = false; peopleOpen = false; abilityOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Sorts</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-2 max-h-[50vh] px-4 relative overflow-y-auto">
|
||||
<div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex gap-2 items-center">
|
||||
<span class="text-xl pe-4" :class="{ 'text-light-red dark:text-dark-red': spellsPoints < (data.spells?.length ?? 0) }">Sorts: {{ data.spells?.length ?? 0 }}/{{ spellsPoints }}</span>
|
||||
<TextInput label="Nom" v-model="spellFilter.text" />
|
||||
<Combobox label="Rang" v-model="spellFilter.ranks" multiple :options="[['Rang 1', 1], ['Rang 2', 2], ['Rang 3', 3]]" />
|
||||
<Combobox label="Type" v-model="spellFilter.types" multiple :options="[['Précision', 'precision'], ['Savoir', 'knowledge'], ['Instinct', 'instinct']]" />
|
||||
<Combobox label="Element" v-model="spellFilter.elements" multiple :options="[['Feu', 'fire'], ['Glace', 'ice'], ['Foudre', 'thunder'], ['Terre', 'earth'], ['Arcane', 'arcana'], ['Air', 'air'], ['Nature', 'nature'], ['Lumière', 'light'], ['Psy', 'psyche']]" />
|
||||
</div>
|
||||
<div class="grid gap-4 grid-cols-2">
|
||||
<div class="py-1 px-2 border border-light-30 dark:border-dark-30 flex flex-col hover:border-light-50 dark:hover:border-dark-50 cursor-pointer" v-for="spell of filterSpells(characterConfig.spells)" :class="{ '!border-accent-blue bg-accent-blue bg-opacity-20': data.spells?.find(e => e === spell.id) }"
|
||||
@click="() => data.spells?.includes(spell.id) ? data.spells.splice(data.spells.findIndex((e: string) => e === spell.id), 1) : data.spells!.push(spell.id)">
|
||||
<div class="flex flex-row justify-between">
|
||||
<span class="text-lg font-bold">{{ spell.name }}</span>
|
||||
<div class="flex flex-row items-center gap-6">
|
||||
<div class="flex flex-row text-sm gap-2">
|
||||
<span v-for="element of spell.elements" :class="elementTexts[element].class">{{ elementTexts[element].text }}</span>
|
||||
</div>
|
||||
<div class="flex flex-row text-sm gap-1">
|
||||
<span class="">Rang {{ spell.rank }}</span><span>/</span>
|
||||
<span class="">{{ spellTypeTexts[spell.type] }}</span><span>/</span>
|
||||
<span class="">{{ spell.cost }} mana</span><span>/</span>
|
||||
<span class="">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MarkdownRenderer :content="spell.effect" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="notesOpen" @update:model-value="() => { trainingOpen = false; peopleOpen = false; abilityOpen = false; spellOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Notes libres</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<Editor class="min-h-[400px] border border-light-30 dark:border-dark-30" v-model="data.notes" />
|
||||
</template>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -2,10 +2,16 @@
|
|||
import config from '#shared/character-config.json';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import PreviewA from '~/components/prose/PreviewA.vue';
|
||||
import type { SpellConfig } from '~/types/character';
|
||||
import { elementTexts, spellTypeTexts, type CharacterConfig } from '~/types/character';
|
||||
|
||||
const characterConfig = config as CharacterConfig;
|
||||
|
||||
const id = useRouter().currentRoute.value.params.id;
|
||||
const { user } = useUserSession();
|
||||
const { data: character, status, error } = await useAsyncData(() => useRequestFetch()(`/api/character/${id}/compiled`));
|
||||
const { add } = useToast();
|
||||
|
||||
const { data: character, status, error } = await useFetch(`/api/character/${id}/compiled`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -20,43 +26,46 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
|
|||
</Head>
|
||||
<div class="flex flex-row gap-4 justify-between">
|
||||
<div></div>
|
||||
<div class="flex flex-row gap-6 items-center justify-center">
|
||||
<Avatar src="" icon="radix-icons:person" size="large" />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xl font-bold">{{ character.name }}</span>
|
||||
<span class="text-sm">De {{ character.username }}</span>
|
||||
<div class="flex lg:flex-row flex-col gap-6 items-center justify-center">
|
||||
<div class="flex gap-6 items-center">
|
||||
<Avatar src="" icon="radix-icons:person" size="large" />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xl font-bold">{{ character.name }}</span>
|
||||
<span class="text-sm">De {{ character.username }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Niveau {{ character.level }}</span>
|
||||
<span>{{ character.race === -1 ? "Race inconnue" : characterConfig.peoples[character.race].name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Niveau {{ character.level }}</span>
|
||||
<span>{{ character.race === -1 ? "Race inconnue" : config.peoples[character.race].name }}</span>
|
||||
<div class="flex gap-6 lg:border-l border-light-30 dark:border-dark-30 py-4 ps-4">
|
||||
<span class="flex flex-row items-center gap-2">PV: {{ character.health - character.values.hp }}/{{ character.health }}</span>
|
||||
<span class="flex flex-row items-center gap-2">Mana: {{ character.mana - character.values.mana }}/{{ character.mana }}</span>
|
||||
</div>
|
||||
<span class="h-full border-l border-light-30 dark:border-dark-30"></span>
|
||||
<span>PV: {{ character.health }}</span>
|
||||
<span>Mana: {{ character.mana }}</span>
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<Tooltip side="right" message="Modifier" v-if="user && user.id === character.owner"><NuxtLink :to="{ name: 'character-id-edit', params: { id: character.id } }"><Button icon><Icon icon="radix-icons:pencil-2" /></Button></NuxtLink></Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col justify-center gap-4 *:py-2">
|
||||
<div class="flex flex-row gap-4 items-center justify-center border-b border-light-30 dark:border-dark-30">
|
||||
<div class="flex relative ps-4">
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.strength }}</span><span>Force</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.dexterity }}</span><span>Dextérité</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.constitution }}</span><span>Constitution</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.intelligence }}</span><span>Intelligence</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.curiosity }}</span><span>Curiosité</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.charisma }}</span><span>Charisme</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.psyche }}</span><span>Psyché</span></div>
|
||||
<div class="grid 2xl:grid-cols-12 grid-cols-2 gap-4 items-center border-b border-light-30 dark:border-dark-30">
|
||||
<div class="flex relative justify-between ps-4 gap-2 2xl:col-span-6 lg:col-span-2">
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.strength }}</span><span class="text-sm 2xl:text-base">Force</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.dexterity }}</span><span class="text-sm 2xl:text-base">Dextérité</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.constitution }}</span><span class="text-sm 2xl:text-base">Constitution</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.intelligence }}</span><span class="text-sm 2xl:text-base">Intelligence</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.curiosity }}</span><span class="text-sm 2xl:text-base">Curiosité</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.charisma }}</span><span class="text-sm 2xl:text-base">Charisme</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.psyche }}</span><span class="text-sm 2xl:text-base">Psyché</span></div>
|
||||
</div>
|
||||
<div class="flex relative border-l border-light-30 dark:border-dark-30 ps-4">
|
||||
<div class="flex relative 2xl:border-l border-light-30 dark:border-dark-30 ps-4 2xl:col-span-2">
|
||||
<div class="flex flex-1 flex-row items-center justify-between">
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.initiative }}</span><span>Initiative</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">{{ character.speed === false ? "Aucun déplacement" : `${character.speed} cases` }}</span><span>Course</span></div>
|
||||
</div>
|
||||
<!-- <div class="absolute top-0 left-0 bottom-0 right-0 bg-light-0 dark:bg-dark-0 bg-opacity-50 dark:bg-opacity-50 text-xl font-bold flex items-center justify-center">Les données secondaires arrivent bientôt.</div> -->
|
||||
</div>
|
||||
<div class="flex relative border-l border-light-30 dark:border-dark-30 ps-4">
|
||||
<div class="flex relative border-l border-light-30 dark:border-dark-30 ps-4 2xl:col-span-4">
|
||||
<div class="flex flex-col px-2">
|
||||
<span class="text-xl">Défense passive: <span class="text-2xl font-bold">{{ character.defense.static }}</span>/+<span class="text-2xl font-bold">{{ character.defense.passivedodge }}</span>/+<span class="text-2xl font-bold">{{ character.defense.passiveparry }}</span></span>
|
||||
<span class="text-xl">Défense active: <span class="float-right">+<span class="text-2xl font-bold">{{ character.defense.activedodge }}</span>/+<span class="text-2xl font-bold">{{ character.defense.activeparry }}</span></span></span>
|
||||
|
|
@ -64,7 +73,7 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
|
|||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 px-8">
|
||||
<div class="flex flex-col pe-8 gap-8 py-8 w-80">
|
||||
<div class="flex flex-col pe-8 gap-4 py-8 w-80 border-r border-light-30 dark:border-dark-30">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30">Maitrise d'arme</span>
|
||||
<div class="grid grid-cols-2 gap-x-3 gap-y-1">
|
||||
|
|
@ -97,35 +106,79 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
|
|||
<span>Sorts d'instinct: <span class="font-bold">{{ character.spellranks.instinct }}</span></span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30 mb-2">Compétences</span>
|
||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30 mb-2 flex items-center gap-4">Résistances (Attaque/Défense) <Tooltip side="right" message="Les défenses affichées incluent déjà leur modifieur de statistique."><Icon icon="radix-icons:question-mark-circled" /></Tooltip></span>
|
||||
<div class="grid grid-cols-3 gap-1">
|
||||
<div class="flex flex-col px-2 items-center" v-for="(value, ability) of character.abilities"><span class="font-bold">+{{ value }}</span><span>{{ config.ability[ability].name }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col border-l border-light-30 dark:border-dark-30 ps-8 gap-4 py-8 max-w-[80rem]">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Actions</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Attaquer - Saisir - Faire chuter - Déplacer - Courir - Pas de coté - Lancer un sort - S'interposer - Se transformer - Utiliser un objet - Anticiper une action - Improviser</span>
|
||||
<MarkdownRenderer :content="character.features.action.join('\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Réactions</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer</span>
|
||||
<MarkdownRenderer :content="character.features.reaction.join('\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Actions libre</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Analyser une situation - Communiquer</span>
|
||||
<MarkdownRenderer :content="character.features.freeaction.join('\n')" />
|
||||
<div class="flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70" v-for="(value, resistance) of character.resistance"><span class="font-bold text-base text-light-100 dark:text-dark-100">+{{ value[0] }}/+{{ value[1] + character.modifier[characterConfig.resistances[resistance].statistic as MainStat] }}</span><span>{{ characterConfig.resistances[resistance].name }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Aptitudes</span>
|
||||
<MarkdownRenderer :content="character.features.misc.map(e => `> ${e}`).join('\n\n')" />
|
||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30 mb-2">Compétences</span>
|
||||
<div class="grid grid-cols-3 gap-1">
|
||||
<div class="flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70" v-for="(value, ability) of character.abilities"><span class="font-bold text-base text-light-100 dark:text-dark-100">+{{ value }}</span><span>{{ characterConfig.abilities[ability].name }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TabsRoot default-value="features" class="w-[60rem]">
|
||||
<TabsList class="flex flex-row gap-4 relative px-4">
|
||||
<TabsIndicator class="absolute px-8 left-0 h-[3px] bottom-0 w-[--radix-tabs-indicator-size] translate-x-[--radix-tabs-indicator-position] transition-[width,transform] duration-300 bg-accent-blue"></TabsIndicator>
|
||||
<TabsTrigger value="features" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Aptitudes</TabsTrigger>
|
||||
<TabsTrigger value="spells" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Sorts</TabsTrigger>
|
||||
<TabsTrigger value="notes" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Notes</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="features">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Actions</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Attaquer - Saisir - Faire chuter - Déplacer - Courir - Pas de coté - Lancer un sort - S'interposer - Se transformer - Utiliser un objet - Anticiper une action - Improviser</span>
|
||||
<MarkdownRenderer :content="character.features.action.join('\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Réactions</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer</span>
|
||||
<MarkdownRenderer :content="character.features.reaction.join('\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Actions libre</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Analyser une situation - Communiquer</span>
|
||||
<MarkdownRenderer :content="character.features.freeaction.join('\n')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Aptitudes</span>
|
||||
<MarkdownRenderer :content="character.features.misc.map(e => `> ${e}`).join('\n\n')" />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent v-if="character.spells.length > 0" value="spells">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
||||
<div class="flex flex-col">
|
||||
<div class="pb-4 px-2 mt-4 border-b last:border-none border-light-30 dark:border-dark-30 flex flex-col" v-for="spell of character.spells.map(e => characterConfig.spells.find((f: SpellConfig) => f.id === e)).filter(e => !!e)">
|
||||
<div class="flex flex-row justify-between">
|
||||
<span class="text-lg font-bold">{{ spell.name }}</span>
|
||||
<div class="flex flex-row items-center gap-6">
|
||||
<div class="flex flex-row text-sm gap-2">
|
||||
<span v-for="element of spell.elements" :class="elementTexts[element].class">{{ elementTexts[element].text }}</span>
|
||||
</div>
|
||||
<div class="flex flex-row text-sm gap-1">
|
||||
<span class="">Rang {{ spell.rank }}</span><span>/</span>
|
||||
<span class="">{{ spellTypeTexts[spell.type] }}</span><span>/</span>
|
||||
<span class="">{{ spell.cost }} mana</span><span>/</span>
|
||||
<span class="">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MarkdownRenderer :content="spell.effect" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="notes">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
||||
<MarkdownRenderer :content="character.notes" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
</TabsRoot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
import type { Progression } from '~/types/character';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
|
||||
definePageMeta({
|
||||
guestsGoesTo: '/user/login',
|
||||
})
|
||||
const { add } = useToast();
|
||||
const { user } = useUserSession();
|
||||
const loading = ref(true);
|
||||
const characters = ref<Array<{ id: number, name: string, progress: Progression }>>([]);
|
||||
characters.value = await useRequestFetch()('/api/character');
|
||||
loading.value = false;
|
||||
|
||||
const { data: characters, error, status } = await useFetch(`/api/character`);
|
||||
|
||||
async function deleteCharacter(id: number)
|
||||
{
|
||||
loading.value = true;
|
||||
status.value = "pending";
|
||||
await useRequestFetch()(`/api/character/${id}`, { method: 'delete' });
|
||||
loading.value = false;
|
||||
status.value = "success";
|
||||
add({ content: 'Personnage supprimé', type: 'info', duration: 25000, timer: true, });
|
||||
characters.value = characters.value?.filter(e => e.id !== id);
|
||||
}
|
||||
async function duplicateCharacter(id: number)
|
||||
{
|
||||
status.value = "pending";
|
||||
const newId = await useRequestFetch()(`/api/character/${id}/duplicate`, { method: 'post' });
|
||||
status.value = "success";
|
||||
add({ content: 'Personnage dupliqué', type: 'info', duration: 25000, timer: true, });
|
||||
useRouter().push({ name: 'character-id', params: { id: newId } });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -26,36 +32,64 @@ async function deleteCharacter(id: number)
|
|||
<Title>d[any] - Mes personnages</Title>
|
||||
</Head>
|
||||
<div class="flex flex-col">
|
||||
<NuxtLink v-if="user?.state === 1" :to="{ name: 'character-id-edit', params: { id: 'new' } }" class="flex align-center justify-center"><Button>Nouveau personnage</Button></NuxtLink>
|
||||
<Tooltip v-else side="top" message="Veuillez valider votre email avant de pouvoir créer un personnage."><Button disabled>Nouveau personnage</Button></Tooltip>
|
||||
<div v-if="loading" class="flex flex-1 justify-center align-center">
|
||||
<div class="flex align-center justify-center">
|
||||
<NuxtLink v-if="user?.state === 1" :to="{ name: 'character-id-edit', params: { id: 'new' } }"><Button>Nouveau personnage</Button></NuxtLink>
|
||||
<Tooltip v-else side="top" message="Veuillez valider votre email avant de pouvoir créer un personnage."><Button disabled>Nouveau personnage</Button></Tooltip>
|
||||
</div>
|
||||
<div v-if="status === 'pending'" class="flex flex-1 justify-center align-center">
|
||||
<Loading size="large" />
|
||||
</div>
|
||||
<div v-else class="grid p-6 grid-cols-4 gap-4">
|
||||
<div class="border border-light-30 dark:border-dark-30 p-1 flex flex-row gap-4" v-for="character of characters">
|
||||
<Avatar size="large" icon="radix-icons:person" src="" class="m-2" />
|
||||
<div class="flex flex-col justify-between w-64">
|
||||
<NuxtLink class="flex-1 text-xl font-bold hover:text-accent-blue truncate" :to="{ name: 'character-id', params: { id: character.id } }" :title="character.name">{{ character.name }}</NuxtLink>
|
||||
<span class="flex-1 text-sm truncate">Niveau {{ character.progress.level }}</span>
|
||||
<div class="flex flex-row gap-8">
|
||||
<NuxtLink class="font-bold text-accent-blue hover:text-opacity-50" :to="{ name: 'character-id-edit', params: { id: character.id } }">Editer</NuxtLink>
|
||||
<AlertDialogRoot>
|
||||
<AlertDialogTrigger asChild><span class="font-bold text-light-red dark:text-dark-red hover:text-opacity-50 cursor-pointer">Supprimer</span></AlertDialogTrigger>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
|
||||
<AlertDialogContent
|
||||
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[800px] translate-x-[-50%] translate-y-[-50%] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 z-50 text-light-100 dark:text-dark-100">
|
||||
<AlertDialogTitle class="text-3xl font-light relative -top-2">Supprimer {{ character.name }} ?</AlertDialogTitle>
|
||||
<div class="flex flex-1 justify-end gap-4">
|
||||
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
|
||||
<AlertDialogAction asChild><Button @click="() => deleteCharacter(character.id)" class="border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red hover:bg-light-redBack dark:hover:bg-dark-redBack text-light-red dark:text-dark-red focus:shadow-light-red dark:focus:shadow-dark-red">Oui</Button></AlertDialogAction>
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</AlertDialogRoot>
|
||||
</div>
|
||||
<div v-else-if="status === 'success'" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full">
|
||||
<div class="border border-light-30 dark:border-dark-30 p-3 flex flex-row gap-4" v-for="character of characters">
|
||||
<Avatar size="large" icon="radix-icons:person" src="" />
|
||||
<div class="flex flex-1 flex-shrink flex-col truncate">
|
||||
<NuxtLink class="text-xl font-bold hover:text-accent-blue truncate" :to="{ name: 'character-id', params: { id: character.id } }" :title="character.name">{{ character.name }}</NuxtLink>
|
||||
<span class="text-sm truncate">Niveau {{ character.level }}</span>
|
||||
</div>
|
||||
<AlertDialogRoot>
|
||||
<DropdownMenuRoot>
|
||||
<DropdownMenuTrigger class="self-start">
|
||||
<Button icon><Icon icon="radix-icons:dots-vertical" /></Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent align="end" side="bottom" class="z-50 outline-none bg-light-20 dark:bg-dark-20 will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade border border-light-35 dark:border-dark-35">
|
||||
<DropdownMenuItem @select="useRouter().push({ name: 'character-id-edit', params: { id: character.id } })" class="cursor-pointer text-base text-light-100 dark:text-dark-100 leading-none flex items-baseline py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-35 dark:data-[highlighted]:bg-dark-35">
|
||||
<Icon icon="radix-icons:pencil-1" class="absolute left-1.5" />
|
||||
<span>Editer</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @select="duplicateCharacter(character.id)" class="cursor-pointer text-base text-light-100 dark:text-dark-100 leading-none flex items-center py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-35 dark:data-[highlighted]:bg-dark-35">
|
||||
<Icon icon="radix-icons:clipboard-copy" class="absolute left-1.5" />
|
||||
<span>Dupliquer</span>
|
||||
</DropdownMenuItem>
|
||||
<AlertDialogTrigger>
|
||||
<DropdownMenuItem class="cursor-pointer text-base text-light-red dark:text-dark-red leading-none flex items-center py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-red dark:data-[highlighted]:bg-dark-red data-[highlighted]:bg-opacity-30 dark:data-[highlighted]:bg-opacity-30">
|
||||
<Icon icon="radix-icons:trash" class="absolute left-1.5" />
|
||||
<span>Supprimer</span>
|
||||
</DropdownMenuItem>
|
||||
</AlertDialogTrigger>
|
||||
|
||||
<DropdownMenuArrow class="fill-light-35 dark:fill-dark-35" />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuRoot>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
|
||||
<AlertDialogContent
|
||||
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[800px] translate-x-[-50%] translate-y-[-50%] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 z-50 text-light-100 dark:text-dark-100">
|
||||
<AlertDialogTitle class="text-3xl font-light relative -top-2">Supprimer {{ character.name }} ?</AlertDialogTitle>
|
||||
<div class="flex flex-1 justify-end gap-4">
|
||||
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
|
||||
<AlertDialogAction asChild><Button @click="() => deleteCharacter(character.id)" class="border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red hover:bg-light-redBack dark:hover:bg-dark-redBack text-light-red dark:text-dark-red focus:shadow-light-red dark:focus:shadow-dark-red">Oui</Button></AlertDialogAction>
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</AlertDialogRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>Erreur de chargement</span>
|
||||
<span>{{ error?.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
const { data: characters, error, status } = await useFetch(`/api/character`, { params: { visibility: "public" } });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>d[any] - Liste des personnages</Title>
|
||||
</Head>
|
||||
<div class="flex flex-col">
|
||||
<div v-if="status === 'pending'" class="flex flex-1 justify-center align-center">
|
||||
<Loading size="large" />
|
||||
</div>
|
||||
<div v-else-if="status === 'success'" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full">
|
||||
<div class="border border-light-30 dark:border-dark-30 p-3 flex flex-row gap-4" v-for="character of characters">
|
||||
<Avatar size="large" icon="radix-icons:person" src="" />
|
||||
<div class="flex flex-1 flex-shrink flex-col truncate">
|
||||
<NuxtLink class="text-xl font-bold hover:text-accent-blue truncate" :to="{ name: 'character-id', params: { id: character.id } }" :title="character.name">{{ character.name }}</NuxtLink>
|
||||
<span class="text-sm truncate">Niveau {{ character.progress.level }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>Erreur de chargement</span>
|
||||
<span>{{ error?.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -5,6 +5,7 @@ import { usersDataTable, usersTable } from '~/db/schema';
|
|||
import { schema } from '~/schemas/registration';
|
||||
import { checkSession, logSession } from '~/server/utils/user';
|
||||
import type { UserSession, UserSessionRequired } from '~/types/auth';
|
||||
import sendMail from '~/server/tasks/mail';
|
||||
|
||||
interface SuccessHandler
|
||||
{
|
||||
|
|
@ -82,7 +83,7 @@ export default defineEventHandler(async (e): Promise<Return> => {
|
|||
id: emailId, timestamp,
|
||||
}
|
||||
});
|
||||
await runTask('mail', {
|
||||
await sendMail({
|
||||
payload: {
|
||||
type: 'mail',
|
||||
to: [body.data.email],
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { eq, or } from 'drizzle-orm';
|
|||
import { z } from 'zod';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { usersTable } from '~/db/schema';
|
||||
import sendMail from '~/server/tasks/mail';
|
||||
|
||||
const schema = z.object({
|
||||
profile: z.string(),
|
||||
|
|
@ -32,7 +33,7 @@ export default defineEventHandler(async (e) => {
|
|||
id, timestamp,
|
||||
}
|
||||
});
|
||||
await runTask('mail', {
|
||||
await sendMail({
|
||||
payload: {
|
||||
type: 'mail',
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { usersDataTable, usersTable } from '~/db/schema';
|
|||
import { schema } from '~/schemas/registration';
|
||||
import { checkSession, logSession } from '~/server/utils/user';
|
||||
import type { UserSession, UserSessionRequired } from '~/types/auth';
|
||||
import sendMail from '~/server/tasks/mail';
|
||||
|
||||
interface SuccessHandler
|
||||
{
|
||||
|
|
@ -73,7 +74,7 @@ export default defineEventHandler(async (e): Promise<Return> => {
|
|||
|
||||
logSession(e, await setUserSession(e, { user: { id: id.id, username: body.data.username, email: body.data.email, state: 0, signin: new Date(), permissions: [], lastTimestamp: new Date(), logCount: 1 } }) as UserSessionRequired);
|
||||
|
||||
await runTask('mail', {
|
||||
await sendMail({
|
||||
payload: {
|
||||
type: 'mail',
|
||||
to: [body.data.email],
|
||||
|
|
|
|||
|
|
@ -1,27 +1,93 @@
|
|||
import { and, eq, sql } from 'drizzle-orm';
|
||||
import { and, eq, SQL, sql, type Operators } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import type { Character } from '~/types/character';
|
||||
import { characterTable, userPermissionsTable } from '~/db/schema';
|
||||
import { hasPermissions } from '~/shared/auth.util';
|
||||
import { group } from '~/shared/general.util';
|
||||
import type { Character, DoubleIndex, Level, MainStat, TrainingLevel } from '~/types/character';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const session = await getUserSession(e);
|
||||
let { visibility } = getQuery(e) as { visibility?: "public" | "own" | "admin" };
|
||||
|
||||
if(!session.user)
|
||||
if(!visibility)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
visibility = "own";
|
||||
}
|
||||
|
||||
let where: ((character: typeof characterTable._.config.columns, sql: Operators) => SQL | undefined) | undefined = undefined;
|
||||
const db = useDatabase();
|
||||
const character = db.select({
|
||||
id: characterTable.id,
|
||||
name: characterTable.name,
|
||||
progress: characterTable.progress,
|
||||
}).from(characterTable).where(eq(characterTable.owner, session.user.id)).all();
|
||||
|
||||
if(character !== undefined)
|
||||
if(visibility === "own")
|
||||
{
|
||||
return character as Character[];
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
where = (character, { eq, and }) => and(eq(character.owner, session.user!.id), eq(character.visibility, "private"));
|
||||
}
|
||||
else if(visibility === 'public')
|
||||
{
|
||||
where = (character, { eq, and }) => eq(character.visibility, "public");
|
||||
}
|
||||
else if(visibility === 'admin')
|
||||
{
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
const db = useDatabase();
|
||||
|
||||
const rights = db.select({ right: userPermissionsTable.permission }).from(userPermissionsTable).where(eq(userPermissionsTable.id, session.user.id)).all();
|
||||
if(rights.length === 0 || !hasPermissions(rights.map(e => e.right), ['admin']))
|
||||
{
|
||||
setResponseStatus(e, 403);
|
||||
return;
|
||||
}
|
||||
|
||||
where = undefined;
|
||||
}
|
||||
|
||||
const characters = db.query.characterTable.findMany({
|
||||
with: {
|
||||
abilities: true,
|
||||
levels: true,
|
||||
modifiers: true,
|
||||
spells: true,
|
||||
training: true,
|
||||
user: {
|
||||
columns: { username: true }
|
||||
}
|
||||
},
|
||||
where: where,
|
||||
}).sync();
|
||||
|
||||
if(characters !== undefined)
|
||||
{
|
||||
return characters.map(character => ({
|
||||
id: character.id,
|
||||
|
||||
name: character.name,
|
||||
people: character.people,
|
||||
level: character.level,
|
||||
aspect: character.aspect,
|
||||
notes: character.notes,
|
||||
health: character.health,
|
||||
mana: character.mana,
|
||||
|
||||
training: character.training.reduce((p, v) => { if(!(v.stat in p)) p[v.stat] = []; p[v.stat].push([v.level as TrainingLevel, v.choice]); return p; }, {} as Record<MainStat, DoubleIndex<TrainingLevel>[]>),
|
||||
leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex<Level>),
|
||||
abilities: group(character.abilities.map(e => ({ ...e, value: [e.value, e.max] as [number, number] })), "ability", "value"),
|
||||
spells: character.spells.map(e => e.value),
|
||||
modifiers: group(character.modifiers, "modifier", "value"),
|
||||
|
||||
owner: character.owner,
|
||||
username: character.user.username,
|
||||
visibility: character.visibility,
|
||||
} as Character));
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import { z } from 'zod';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import { characterAbilitiesTable, characterLevelingTable, characterModifiersTable, characterSpellsTable, characterTable, characterTrainingTable } from '~/db/schema';
|
||||
import { CharacterValidation, type Ability, type DoubleIndex, type MainStat, type TrainingLevel } from '~/types/character';
|
||||
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const body = await readBody(e);
|
||||
if(!body)
|
||||
const body = await readValidatedBody(e, CharacterValidation.extend({ id: z.unknown(), }).safeParse);
|
||||
if(!body.success)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
return body.error.message;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
|
|
@ -18,12 +21,46 @@ export default defineEventHandler(async (e) => {
|
|||
|
||||
const db = useDatabase();
|
||||
|
||||
const id = await db.insert(characterTable).values({
|
||||
name: body.name,
|
||||
progress: body.progress,
|
||||
owner: session.user.id,
|
||||
}).returning({ id: characterTable.id });
|
||||
try
|
||||
{
|
||||
const id = db.transaction((tx) => {
|
||||
const id = tx.insert(characterTable).values({
|
||||
name: body.data.name,
|
||||
owner: session.user!.id,
|
||||
people: body.data.people!,
|
||||
level: body.data.level,
|
||||
aspect: body.data.aspect,
|
||||
notes: body.data.notes,
|
||||
health: body.data.health,
|
||||
mana: body.data.mana,
|
||||
visibility: body.data.visibility,
|
||||
thumbnail: body.data.thumbnail,
|
||||
}).returning({ id: characterTable.id }).get().id;
|
||||
|
||||
if(body.data.leveling.length > 0) tx.insert(characterLevelingTable).values(body.data.leveling.map(e => ({ character: id, level: e[0], choice: e[1] }))).run();
|
||||
|
||||
setResponseStatus(e, 201);
|
||||
return id[0].id;
|
||||
const training = Object.entries(body.data.training).flatMap(e => e[1].map(_e => ({ character: id, stat: e[0] as MainStat, level: _e[0], choice: _e[1] })));
|
||||
if(training.length > 0) tx.insert(characterTrainingTable).values(training).run();
|
||||
|
||||
const modifiers = Object.entries(body.data.modifiers).map((e) => ({ character: id, modifier: e[0] as MainStat, value: e[1] }));
|
||||
if(modifiers.length > 0) tx.insert(characterModifiersTable).values(modifiers).run();
|
||||
|
||||
if(body.data.spells.length > 0) tx.insert(characterSpellsTable).values(body.data.spells.map(e => ({ character: id, value: e }))).run();
|
||||
|
||||
const abilities = Object.entries(body.data.abilities).map(e => ({ character: id, ability: e[0] as Ability, value: e[1][0], max: e[1][1] }));
|
||||
if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run();
|
||||
|
||||
return id;
|
||||
});
|
||||
|
||||
setResponseStatus(e, 201);
|
||||
return id;
|
||||
}
|
||||
catch(_e)
|
||||
{
|
||||
console.error(_e);
|
||||
|
||||
setResponseStatus(e, 500);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { and, eq, sql } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import type { Character } from '~/types/character';
|
||||
import { group } from '~/shared/general.util';
|
||||
import type { Character, DoubleIndex, Level, MainStat, TrainingLevel } from '~/types/character';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
|
|
@ -21,16 +22,43 @@ export default defineEventHandler(async (e) => {
|
|||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const character = db.select({
|
||||
id: characterTable.id,
|
||||
name: characterTable.name,
|
||||
progress: characterTable.progress,
|
||||
owner: characterTable.owner
|
||||
}).from(characterTable).where(and(eq(characterTable.id, id), eq(characterTable.owner, session.user.id))).get();
|
||||
const character = db.query.characterTable.findFirst({
|
||||
with: {
|
||||
abilities: true,
|
||||
levels: true,
|
||||
modifiers: true,
|
||||
spells: true,
|
||||
training: true,
|
||||
user: {
|
||||
columns: { username: true }
|
||||
}
|
||||
},
|
||||
where: (character, { eq, and }) => and(eq(character.id, parseInt(id, 10)), eq(characterTable.owner, session.user!.id)),
|
||||
}).sync();
|
||||
|
||||
if(character !== undefined)
|
||||
{
|
||||
return character as Character;
|
||||
return {
|
||||
id: character.id,
|
||||
|
||||
name: character.name,
|
||||
people: character.people,
|
||||
level: character.level,
|
||||
aspect: character.aspect,
|
||||
notes: character.notes,
|
||||
health: character.health,
|
||||
mana: character.mana,
|
||||
|
||||
training: character.training.reduce((p, v) => { if(!(v.stat in p)) p[v.stat] = []; p[v.stat].push([v.level as TrainingLevel, v.choice]); return p; }, {} as Record<MainStat, DoubleIndex<TrainingLevel>[]>),
|
||||
leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex<Level>),
|
||||
abilities: group(character.abilities.map(e => ({ ...e, value: [e.value, e.max] as [number, number] })), "ability", "value"),
|
||||
spells: character.spells.map(e => e.value),
|
||||
modifiers: group(character.modifiers, "modifier", "value"),
|
||||
|
||||
owner: character.owner,
|
||||
username: character.user.username,
|
||||
visibility: character.visibility,
|
||||
} as Character;
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import { characterAbilitiesTable, characterLevelingTable, characterModifiersTable, characterSpellsTable, characterTable, characterTrainingTable } from '~/db/schema';
|
||||
import { CharacterValidation, type Ability, type MainStat } from '~/types/character';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
if(!id)
|
||||
const params = getRouterParam(e, "id");
|
||||
if(!params)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
const id = parseInt(params, 10);
|
||||
|
||||
const body = await readBody(e);
|
||||
if(!body)
|
||||
const body = await readValidatedBody(e, CharacterValidation.safeParse);
|
||||
if(!body.success)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
return body.error.message;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const old = db.select({ id: characterTable.id, owner: characterTable.owner }).from(characterTable).where(eq(characterTable.id, parseInt(id))).get();
|
||||
const old = db.select({ id: characterTable.id, owner: characterTable.owner }).from(characterTable).where(eq(characterTable.id, id)).get();
|
||||
|
||||
if(!old)
|
||||
{
|
||||
|
|
@ -32,11 +34,39 @@ export default defineEventHandler(async (e) => {
|
|||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
db.transaction((tx) => {
|
||||
tx.update(characterTable).set({
|
||||
name: body.data.name,
|
||||
people: body.data.people!,
|
||||
level: body.data.level,
|
||||
aspect: body.data.aspect,
|
||||
notes: body.data.notes,
|
||||
health: body.data.health,
|
||||
mana: body.data.mana,
|
||||
visibility: body.data.visibility,
|
||||
thumbnail: body.data.thumbnail,
|
||||
}).where(eq(characterTable.id, id)).run();
|
||||
|
||||
db.update(characterTable).set({
|
||||
name: body.name,
|
||||
progress: body.progress,
|
||||
}).where(eq(characterTable.id, parseInt(id))).run();
|
||||
tx.delete(characterLevelingTable).where(eq(characterLevelingTable.character, id)).run();
|
||||
tx.delete(characterTrainingTable).where(eq(characterTrainingTable.character, id)).run();
|
||||
tx.delete(characterModifiersTable).where(eq(characterModifiersTable.character, id)).run();
|
||||
tx.delete(characterSpellsTable).where(eq(characterSpellsTable.character, id)).run();
|
||||
tx.delete(characterAbilitiesTable).where(eq(characterAbilitiesTable.character, id)).run();
|
||||
|
||||
if(body.data.leveling.length > 0) tx.insert(characterLevelingTable).values(body.data.leveling.map(e => ({ character: id, level: e[0], choice: e[1] }))).run();
|
||||
|
||||
const training = Object.entries(body.data.training).flatMap(e => e[1].map(_e => ({ character: id, stat: e[0] as MainStat, level: _e[0], choice: _e[1] })));
|
||||
if(training.length > 0) tx.insert(characterTrainingTable).values(training).run();
|
||||
|
||||
const modifiers = Object.entries(body.data.modifiers).map((e) => ({ character: id, modifier: e[0] as MainStat, value: e[1] }));
|
||||
if(modifiers.length > 0) tx.insert(characterModifiersTable).values(modifiers).run();
|
||||
|
||||
if(body.data.spells.length > 0) tx.insert(characterSpellsTable).values(body.data.spells.map(e => ({ character: id, value: e }))).run();
|
||||
|
||||
const abilities = Object.entries(body.data.abilities).map(e => ({ character: id, ability: e[0] as Ability, value: e[1][0], max: e[1][1] }));
|
||||
if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run();
|
||||
});
|
||||
|
||||
await useStorage('cache').removeItem(`nitro:functions:character:${id}.json`);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { and, eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import type { Character, CharacterConfig, CompiledCharacter, DoubleIndex, Feature, MainStat, TrainingLevel, TrainingOption } from '~/types/character';
|
||||
import { defaultCharacter, type Ability, type Character, type CharacterConfig, type CompiledCharacter, type DoubleIndex, type Feature, type Level, type MainStat, type TrainingLevel, type TrainingOption } from '~/types/character';
|
||||
import characterData from '#shared/character-config.json';
|
||||
import { users } from '~/drizzle/schema';
|
||||
import { group } from '~/shared/general.util';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
|
|
@ -14,109 +12,55 @@ export default defineEventHandler(async (e) => {
|
|||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const character = db.select({
|
||||
id: characterTable.id,
|
||||
name: characterTable.name,
|
||||
progress: characterTable.progress,
|
||||
owner: characterTable.owner,
|
||||
username: users.username
|
||||
}).from(characterTable).leftJoin(users, eq(characterTable.owner, users.id)).where(and(eq(characterTable.id, parseInt(id)))).get();
|
||||
const character = db.query.characterTable.findFirst({
|
||||
with: {
|
||||
abilities: true,
|
||||
levels: true,
|
||||
modifiers: true,
|
||||
spells: true,
|
||||
training: true,
|
||||
user: {
|
||||
columns: { username: true }
|
||||
}
|
||||
},
|
||||
where: (character, { eq }) => eq(character.id, parseInt(id, 10)),
|
||||
}).sync();
|
||||
|
||||
if(character !== undefined)
|
||||
{
|
||||
return compileCharacter(character as Character & { username: string });
|
||||
return compileCharacter(Object.assign(defaultCharacter, {
|
||||
id: character.id,
|
||||
|
||||
name: character.name,
|
||||
people: character.people,
|
||||
level: character.level,
|
||||
aspect: character.aspect,
|
||||
notes: character.notes,
|
||||
health: character.health,
|
||||
mana: character.mana,
|
||||
|
||||
training: character.training.reduce((p, v) => { if(!(v.stat in p)) p[v.stat] = []; p[v.stat].push([v.level as TrainingLevel, v.choice]); return p; }, {} as Record<MainStat, DoubleIndex<TrainingLevel>[]>),
|
||||
leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex<Level>),
|
||||
abilities: group(character.abilities.map(e => ({ ...e, value: [e.value, e.max] as [number, number] })), "ability", "value"),
|
||||
spells: character.spells.map(e => e.value),
|
||||
modifiers: group(character.modifiers, "modifier", "value"),
|
||||
|
||||
owner: character.owner,
|
||||
username: character.user.username,
|
||||
visibility: character.visibility,
|
||||
} as Character) as Character);
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}/* , { name: "character", getKey: (e) => getRouterParam(e, "id") || 'error' } */);
|
||||
|
||||
/*
|
||||
Athlétisme
|
||||
La capacité à effectuer un acte physique intense ou prolongé. Permet de pousser, contraindre, nager, courir.
|
||||
|
||||
Force + Constitution.
|
||||
|
||||
Acrobatique
|
||||
La capacité à se mouvoir avec souplesse sous la contrainte. Permet d'escalader, d'enjamber, de sauter.
|
||||
|
||||
Force + Dextérité.
|
||||
|
||||
Intimidation
|
||||
La capacité à intimider et inspirer la crainte.
|
||||
|
||||
Force + Charisme.
|
||||
|
||||
Doigté
|
||||
La capacité à faire des actions précises avec ses mains. Permet de voler à la tire, de crocheter.
|
||||
|
||||
Dextérité + Dextérité.
|
||||
|
||||
Discrétion
|
||||
La capacité à dissimuler sa présence. Permet de se cacher, de se mouvoir sans bruit.
|
||||
|
||||
Dextérité + Dextérité.
|
||||
|
||||
Survie
|
||||
La capacité à survivre dans des conditions difficiles. Permet de pister, de collecter de la nourriture, de retrouver son chemin.
|
||||
|
||||
Constitution + Psyché.
|
||||
|
||||
Enquête
|
||||
La capacité à demander au MJ de l'aide parce que vous puez la merde.
|
||||
|
||||
Intelligence + Curiosité.
|
||||
|
||||
Histoire
|
||||
La capacité à connaitre le passé du monde.
|
||||
|
||||
Intelligence + Curiosité.
|
||||
|
||||
Religion
|
||||
La capacité a connaitre les pratiques et les coutumes religieuses.
|
||||
|
||||
Intelligence + Curiosité.
|
||||
|
||||
Arcanes
|
||||
La capacité à comprendre et percevoir la magie. Permet de comprendre un sort en cours, de détecter de la magie.
|
||||
|
||||
Intelligence + Psyché.
|
||||
|
||||
Compréhension
|
||||
La capacité à déterminer les intentions des interlocuteurs. Permet de déceler des mensonges, de l'influence.
|
||||
|
||||
Intelligence + Charisme.
|
||||
|
||||
Perception
|
||||
La capacité à observer le monde à travers ces sens. Permet d'observer, d'entendre, de sentir.
|
||||
|
||||
Curiosité + Curiosité.
|
||||
|
||||
Représentation
|
||||
La capacité à se mettre en scène et à utiliser les arts. Permet de se produire en spectacle, de jouer d'un instrument, de chanter, de danser.
|
||||
|
||||
Curiosité + Charisme.
|
||||
|
||||
Médicine
|
||||
La capacité à apporter des soins. Permet de stabiliser un joueur mourant, de soigner durant un repos.
|
||||
|
||||
Curiosité + Psyché.
|
||||
|
||||
Persuasion
|
||||
Charisme + Psyché.
|
||||
|
||||
Dressage
|
||||
Charisme + Psyché.
|
||||
|
||||
Mensonge
|
||||
Charisme + Psyché.
|
||||
*/
|
||||
function compileCharacter(character: Character & { username?: string }): CompiledCharacter
|
||||
{
|
||||
const config = characterData as CharacterConfig;
|
||||
const race = character.progress.race.index !== undefined ? config.peoples[character.progress.race.index] : undefined;
|
||||
const raceOptions = race ? character.progress.race.progress!.map(e => race.options[e[0]][e[1]]) : [];
|
||||
const features = Object.entries(config.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, character.progress.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][];
|
||||
const race = character.people !== undefined ? config.peoples[character.people] : undefined;
|
||||
const raceOptions = race ? character.leveling!.map(e => race.options[e[0]][e[1]]) : [];
|
||||
const features = Object.entries(config.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, character.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][];
|
||||
|
||||
const compiled: CompiledCharacter = {
|
||||
id: character.id,
|
||||
|
|
@ -125,9 +69,13 @@ function compileCharacter(character: Character & { username?: string }): Compile
|
|||
name: character.name,
|
||||
health: raceOptions.reduce((p, v) => p + (v.health ?? 0), 0),
|
||||
mana: raceOptions.reduce((p, v) => p + (v.mana ?? 0), 0),
|
||||
race: character.progress.race.index ?? -1,
|
||||
modifier: features.map(e => [e[0], Math.floor(e[1].length / 3)] as [MainStat, number]).reduce((p, v) => { p[v[0]] = v[1]; return p }, {} as Record<MainStat, number>),
|
||||
level: character.progress.level,
|
||||
race: character.people!,
|
||||
modifier: features.map(e => [e[0], Math.floor((e[1].length - 1) / 3) + (character.modifiers[e[0]] ?? 0)] as [MainStat, number]).reduce((p, v) => { p[v[0]] = v[1]; return p }, {} as Record<MainStat, number>),
|
||||
level: character.level,
|
||||
values: {
|
||||
health: character.health,
|
||||
mana: character.mana
|
||||
},
|
||||
features: {
|
||||
action: [],
|
||||
reaction: [],
|
||||
|
|
@ -154,11 +102,14 @@ function compileCharacter(character: Character & { username?: string }): Compile
|
|||
deception: 0
|
||||
},
|
||||
spellslots: 0,
|
||||
artslots: 0,
|
||||
spellranks: {
|
||||
instinct: 0,
|
||||
knowledge: 0,
|
||||
precision: 0
|
||||
precision: 0,
|
||||
arts: 0,
|
||||
},
|
||||
spells: character.spells ?? [],
|
||||
speed: false,
|
||||
defense: {
|
||||
static: 6,
|
||||
|
|
@ -180,7 +131,7 @@ function compileCharacter(character: Character & { username?: string }): Compile
|
|||
resistance: {
|
||||
stun: [0, 0],
|
||||
bleed: [0, 0],
|
||||
posion: [0, 0],
|
||||
poison: [0, 0],
|
||||
fear: [0, 0],
|
||||
influence: [0, 0],
|
||||
charm: [0, 0],
|
||||
|
|
@ -191,13 +142,14 @@ function compileCharacter(character: Character & { username?: string }): Compile
|
|||
},
|
||||
initiative: 0,
|
||||
aspect: "",
|
||||
notes: character.notes ?? "",
|
||||
};
|
||||
|
||||
features.forEach(e => e[1].forEach((_e, i) => applyTrainingOption(e[0], _e, compiled, i === e[1].length - 1)));
|
||||
specialFeatures(compiled, character.progress.training);
|
||||
specialFeatures(compiled, character.training);
|
||||
|
||||
Object.entries(character.abilities).forEach(e => compiled.abilities[e[0] as Ability]! += e[1][0]);
|
||||
|
||||
Object.entries(character.progress.abilities).forEach(e => compiled.abilities[e[0]] += e[1][0]);
|
||||
|
||||
return compiled;
|
||||
}
|
||||
function applyTrainingOption(stat: MainStat, option: TrainingOption, character: CompiledCharacter, last: boolean)
|
||||
|
|
@ -210,6 +162,9 @@ function applyTrainingOption(stat: MainStat, option: TrainingOption, character:
|
|||
if(option.spellrank) character.spellranks[option.spellrank]++;
|
||||
if(option.defense) option.defense.forEach(e => character.defense[e]++);
|
||||
if(option.resistance) option.resistance.forEach(e => character.resistance[e[0]][e[1] === "attack" ? 0 : 1]++);
|
||||
if(option.spellslot) character.spellslots += option.spellslot in character.modifier ? character.modifier[option.spellslot as MainStat] : option.spellslot as number;
|
||||
if(option.arts) character.artslots += option.arts in character.modifier ? character.modifier[option.arts as MainStat] : option.arts as number;
|
||||
if(option.spell) character.spells.push(option.spell);
|
||||
|
||||
option.description.forEach(line => !line.disposable && (last || !line.replaced) && character.features[line.category ?? "misc"].push(line.text));
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
if(!id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const old = db.select().from(characterTable).where(eq(characterTable.id, parseInt(id, 10))).get();
|
||||
|
||||
if(!old)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user || old.owner !== session.user.id || session.user.state !== 1)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const returned = await db.insert(characterTable).values({
|
||||
name: `Copie de ${old.name}`,
|
||||
progress: old.progress,
|
||||
owner: session.user.id,
|
||||
}).returning({ id: characterTable.id });
|
||||
|
||||
setResponseStatus(e, 201);
|
||||
return returned[0].id;
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { and, eq, sql } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import type { Character, CharacterValues } from '~/types/character';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
|
||||
if(!id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const character = db.select({
|
||||
values: characterTable.values
|
||||
}).from(characterTable).where(and(eq(characterTable.id, parseInt(id, 10)), eq(characterTable.owner, session.user.id))).get();
|
||||
|
||||
if(character !== undefined)
|
||||
{
|
||||
return character.values as CharacterValues;
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
});
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
if(!id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = await readBody(e);
|
||||
if(!body)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const old = db.select({ id: characterTable.id, owner: characterTable.owner }).from(characterTable).where(eq(characterTable.id, parseInt(id, 10))).get();
|
||||
|
||||
if(!old)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user || old.owner !== session.user.id || session.user.state !== 1)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
db.update(characterTable).set({
|
||||
values: body,
|
||||
}).where(eq(characterTable.id, parseInt(id, 10))).run();
|
||||
|
||||
setResponseStatus(e, 200);
|
||||
return;
|
||||
});
|
||||
|
|
@ -2,6 +2,7 @@ import { hash } from "bun";
|
|||
import { eq } from "drizzle-orm";
|
||||
import useDatabase from "~/composables/useDatabase";
|
||||
import { usersTable } from "~/db/schema";
|
||||
import sendMail from '~/server/tasks/mail';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const session = await getUserSession(e);
|
||||
|
|
@ -56,7 +57,7 @@ export default defineEventHandler(async (e) => {
|
|||
id: emailId, timestamp,
|
||||
}
|
||||
});
|
||||
await runTask('mail', {
|
||||
await sendMail({
|
||||
payload: {
|
||||
type: 'mail',
|
||||
to: [data.email],
|
||||
|
|
|
|||
|
|
@ -28,69 +28,78 @@ const transport = nodemailer.createTransport({
|
|||
pool: true,
|
||||
host: config.mail.host,
|
||||
port: config.mail.port,
|
||||
secure: true,
|
||||
secure: config.mail.port === "465",
|
||||
auth: {
|
||||
user: config.mail.user,
|
||||
pass: config.mail.passwd,
|
||||
},
|
||||
requireTLS: true,
|
||||
tls: { rejectUnauthorized: false },
|
||||
dkim: {
|
||||
domainName: domain,
|
||||
keySelector: selector,
|
||||
privateKey: dkim,
|
||||
},
|
||||
proxy: config.mail.proxy,
|
||||
});
|
||||
|
||||
export default defineTask({
|
||||
meta: {
|
||||
name: 'mail',
|
||||
description: 'Send email',
|
||||
},
|
||||
async run(e) {
|
||||
try {
|
||||
if(e.payload.type !== 'mail')
|
||||
{
|
||||
throw new Error(`Données inconnues`);
|
||||
}
|
||||
|
||||
const payload = e.payload as MailPayload;
|
||||
const template = templates[payload.template];
|
||||
|
||||
if(!template)
|
||||
{
|
||||
throw new Error(`Modèle de mail ${payload.template} inconnu`);
|
||||
}
|
||||
|
||||
console.time('Generating HTML');
|
||||
const mail: Mail.Options = {
|
||||
from: 'd[any] - Ne pas répondre <no-reply@peaceultime.com>',
|
||||
to: payload.to,
|
||||
html: await render(template.component, payload.data),
|
||||
subject: template.subject,
|
||||
textEncoding: 'quoted-printable',
|
||||
};
|
||||
console.timeEnd('Generating HTML');
|
||||
|
||||
if(mail.html === '')
|
||||
return { result: false, error: new Error("Invalid content") };
|
||||
|
||||
console.time('Sending Mail');
|
||||
const status = await transport.sendMail(mail);
|
||||
console.timeEnd('Sending Mail');
|
||||
|
||||
if(status.rejected.length > 0)
|
||||
{
|
||||
return { result: false, error: status.response, details: status.rejectedErrors };
|
||||
}
|
||||
|
||||
return { result: true };
|
||||
}
|
||||
catch(e)
|
||||
if(process.env.NODE_ENV === 'production')
|
||||
{
|
||||
transport.verify((error) => {
|
||||
if(error)
|
||||
{
|
||||
return { result: false, error: e };
|
||||
console.log('Mail server cannot be reached');
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
})
|
||||
else
|
||||
console.log("Mail server is reachable and ready to communicate");
|
||||
});
|
||||
}
|
||||
|
||||
export default async function(e: TaskEvent) {
|
||||
try {
|
||||
if(e.payload.type !== 'mail')
|
||||
{
|
||||
throw new Error(`Données inconnues`);
|
||||
}
|
||||
|
||||
const payload = e.payload as MailPayload;
|
||||
const template = templates[payload.template];
|
||||
|
||||
if(!template)
|
||||
{
|
||||
throw new Error(`Modèle de mail ${payload.template} inconnu`);
|
||||
}
|
||||
|
||||
console.time('Generating HTML');
|
||||
const mail: Mail.Options = {
|
||||
from: 'd[any] - Ne pas répondre <no-reply@peaceultime.com>',
|
||||
to: payload.to,
|
||||
html: await render(template.component, payload.data),
|
||||
subject: template.subject,
|
||||
textEncoding: 'quoted-printable',
|
||||
};
|
||||
console.timeEnd('Generating HTML');
|
||||
|
||||
if(mail.html === '')
|
||||
return { result: false, error: new Error("Invalid content") };
|
||||
|
||||
console.time('Sending Mail');
|
||||
const status = await transport.sendMail(mail);
|
||||
console.timeEnd('Sending Mail');
|
||||
|
||||
if(status.rejected.length > 0)
|
||||
{
|
||||
return { result: false, error: status.response, details: status.rejectedErrors };
|
||||
}
|
||||
|
||||
return { result: true };
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.error(e);
|
||||
return { result: false, error: e };
|
||||
}
|
||||
}
|
||||
|
||||
async function render(component: any, data: Record<string, any>): Promise<string>
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -12,11 +12,26 @@ export function unifySlug(slug: string | string[]): string
|
|||
{
|
||||
return (Array.isArray(slug) ? slug.join('/') : slug);
|
||||
}
|
||||
export function group<
|
||||
T,
|
||||
K extends keyof T,
|
||||
V extends keyof T,
|
||||
KeyType extends string | number | symbol = Extract<T[K], string | number | symbol>
|
||||
>(
|
||||
table: T[],
|
||||
key: K & (T[K] extends string | number | symbol ? K : never),
|
||||
value: V
|
||||
): Record<KeyType, T[V]> {
|
||||
return table.reduce((p, v) => {
|
||||
p[v[key] as KeyType] = v[value];
|
||||
return p;
|
||||
}, {} as Record<KeyType, T[V]>);
|
||||
}
|
||||
export function parsePath(path: string): string
|
||||
{
|
||||
return path.toLowerCase().trim().replaceAll(" ", "-").normalize("NFD").replaceAll(/[\u0300-\u036f]/g, "").replaceAll('(', '').replaceAll(')', '');
|
||||
}
|
||||
export function parseId(id: string | undefined): string |undefined
|
||||
export function parseId(id: string | undefined): string | undefined
|
||||
{
|
||||
return id;
|
||||
return id?.normalize('NFD')?.replace(/[\u0300-\u036f]/g, '')?.replace(/^\d\. */g, '')?.replace(/\s/g, "-")?.replace(/%/g, "-percent")?.replace(/\?/g, "-q")?.toLowerCase();
|
||||
|
|
|
|||
|
|
@ -1,151 +0,0 @@
|
|||
export type MainStat = "strength" | "dexterity" | "constitution" | "intelligence" | "curiosity" | "charisma" | "psyche";
|
||||
export type Ability = "athletics" | "acrobatics" | "intimidation" | "sleightofhand" | "stealth" | "survival" | "investigation" | "history" | "religion" | "arcana" | "understanding" | "perception" | "performance" | "medecine" | "persuasion" | "animalhandling" | "deception";
|
||||
export type Level = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20;
|
||||
export type TrainingLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;
|
||||
export type SpellType = "precision" | "knowledge" | "instinct";
|
||||
export type Category = "action" | "reaction" | "freeaction" | "misc";
|
||||
export type Resistance = keyof CompiledCharacter["resistance"];
|
||||
|
||||
export type DoubleIndex<T extends number | string> = [T, number];
|
||||
|
||||
export type Progression = {
|
||||
training: Record<MainStat, DoubleIndex<TrainingLevel>[]>;
|
||||
race: {
|
||||
index?: number;
|
||||
progress?: DoubleIndex<Level>[];
|
||||
};
|
||||
level: number;
|
||||
abilities: Partial<Record<Ability, [number, number]>>; //First is the ability, second is the max increment
|
||||
spells?: string[]; //Spell ID
|
||||
modifiers?: Partial<Record<MainStat, number>>;
|
||||
aspect?: string;
|
||||
};
|
||||
export type Character = {
|
||||
id: number;
|
||||
name: string;
|
||||
progress: Progression;
|
||||
owner?: number;
|
||||
};
|
||||
export type CharacterConfig = {
|
||||
peoples: Race[],
|
||||
training: Record<MainStat, Record<TrainingLevel, TrainingOption[]>>;
|
||||
ability: Record<Ability, AbilityConfig>;
|
||||
};
|
||||
export type AbilityConfig = {
|
||||
max: [MainStat, MainStat];
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
export type Race = {
|
||||
name: string;
|
||||
description: string;
|
||||
utils: {
|
||||
maxOption: number;
|
||||
};
|
||||
options: Record<Level, RaceOption[]>;
|
||||
};
|
||||
export type RaceOption = {
|
||||
training?: number;
|
||||
health?: number;
|
||||
mana?: number;
|
||||
spec?: number;
|
||||
shaping?: number;
|
||||
modifier?: number;
|
||||
};
|
||||
export type Feature = {
|
||||
text?: string;
|
||||
} & (ActionFeature | ReactionFeature | FreeActionFeature | BonusFeature | MiscFeature);
|
||||
type ActionFeature = {
|
||||
type: "action";
|
||||
cost: 1 | 2 | 3;
|
||||
text: string;
|
||||
};
|
||||
type ReactionFeature = {
|
||||
type: "reaction";
|
||||
text: string;
|
||||
};
|
||||
type FreeActionFeature = {
|
||||
type: "freeaction";
|
||||
text: string;
|
||||
};
|
||||
type BonusFeature = {
|
||||
type: "bonus";
|
||||
action: "add" | "remove" | "set" | "cap";
|
||||
value: number;
|
||||
property: string;
|
||||
};
|
||||
type MiscFeature = {
|
||||
type: "misc";
|
||||
text: string;
|
||||
};
|
||||
export type TrainingOption = {
|
||||
description: Array<{
|
||||
text: string;
|
||||
disposable?: boolean;
|
||||
replaced?: boolean;
|
||||
category?: Category;
|
||||
}>;
|
||||
mana?: number;
|
||||
health?: number;
|
||||
modifier?: number;
|
||||
ability?: number;
|
||||
speed?: false | number;
|
||||
initiative?: number;
|
||||
mastery?: keyof CompiledCharacter["mastery"];
|
||||
spellrank?: SpellType;
|
||||
spellslot?: number | MainStat;
|
||||
defense?: Array<keyof CompiledCharacter["defense"]>;
|
||||
resistance?: [Resistance, "attack" | "defense"][];
|
||||
features?: Feature[]; //TODO
|
||||
};
|
||||
export type CompiledCharacter = {
|
||||
id: number;
|
||||
owner?: number;
|
||||
username?: string;
|
||||
name: string;
|
||||
health: number;
|
||||
mana: number;
|
||||
race: number;
|
||||
spellslots: number;
|
||||
spellranks: Record<SpellType, 0 | 1 | 2 | 3>;
|
||||
aspect: string;
|
||||
speed: number | false;
|
||||
initiative: number;
|
||||
|
||||
defense: {
|
||||
static: number;
|
||||
activeparry: number;
|
||||
activedodge: number;
|
||||
passiveparry: number;
|
||||
passivedodge: number;
|
||||
};
|
||||
|
||||
mastery: {
|
||||
strength: number;
|
||||
dexterity: number;
|
||||
shield: number;
|
||||
armor: number;
|
||||
multiattack: number;
|
||||
magicpower: number;
|
||||
magicspeed: number;
|
||||
magicelement: number;
|
||||
};
|
||||
|
||||
resistance: { //First is attack, second is defense
|
||||
stun: [number, number];
|
||||
bleed: [number, number];
|
||||
posion: [number, number];
|
||||
fear: [number, number];
|
||||
influence: [number, number];
|
||||
charm: [number, number];
|
||||
possesion: [number, number];
|
||||
precision: [number, number];
|
||||
knowledge: [number, number];
|
||||
instinct: [number, number];
|
||||
};
|
||||
|
||||
modifier: Record<MainStat, number>;
|
||||
abilities: Partial<Record<Ability, number>>;
|
||||
level: number;
|
||||
features: Record<Category, string[]>; //Currently: List of training option as text. TODO: Update to a more complex structure later
|
||||
};
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
import { z, type ZodRawShape } from "zod";
|
||||
import { characterTable } from "~/db/schema";
|
||||
|
||||
export const MAIN_STATS = ["strength","dexterity","constitution","intelligence","curiosity","charisma","psyche"] as const; export type MainStat = typeof MAIN_STATS[number];
|
||||
export const ABILITIES = ["athletics","acrobatics","intimidation","sleightofhand","stealth","survival","investigation","history","religion","arcana","understanding","perception","performance","medecine","persuasion","animalhandling","deception"] as const; export type Ability = typeof ABILITIES[number];
|
||||
export const LEVELS = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] as const; export type Level = typeof LEVELS[number];
|
||||
export const TRAINING_LEVELS = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] as const; export type TrainingLevel = typeof TRAINING_LEVELS[number];
|
||||
export const SPELL_TYPES = ["precision","knowledge","instinct","arts"] as const; export type SpellType = typeof SPELL_TYPES[number];
|
||||
export const CATEGORIES = ["action","reaction","freeaction","misc"] as const; export type Category = typeof CATEGORIES[number];
|
||||
export const SPELL_ELEMENTS = ["fire","ice","thunder","earth","arcana","air","nature","light","psyche"] as const; export type SpellElement = typeof SPELL_ELEMENTS[number];
|
||||
export const RESISTANCES = ["stun","bleed","poison","fear","influence","charm","possesion","precision","knowledge","instinct"] as const; export type Resistance = typeof RESISTANCES[number];
|
||||
|
||||
export type DoubleIndex<T extends number | string> = [T, number];
|
||||
|
||||
export const defaultCharacter: Character = {
|
||||
id: -1,
|
||||
|
||||
name: "",
|
||||
people: undefined,
|
||||
level: 1,
|
||||
health: 0,
|
||||
mana: 0,
|
||||
|
||||
training: MAIN_STATS.reduce((p, v) => { p[v] = [[0, 0]]; return p; }, {} as Record<MainStat, DoubleIndex<TrainingLevel>[]>),
|
||||
leveling: [[1, 0]],
|
||||
abilities: {},
|
||||
spells: [],
|
||||
modifiers: {},
|
||||
|
||||
owner: -1,
|
||||
visibility: "private",
|
||||
};
|
||||
export const mainStatTexts: Record<MainStat, string> = {
|
||||
"strength": "Force",
|
||||
"dexterity": "Dextérité",
|
||||
"constitution": "Constitution",
|
||||
"intelligence": "Intelligence",
|
||||
"curiosity": "Curiosité",
|
||||
"charisma": "Charisme",
|
||||
"psyche": "Psyché",
|
||||
}
|
||||
export const elementTexts: Record<SpellElement, { class: string, text: string }> = {
|
||||
fire: { class: 'text-light-red dark:text-dark-red', text: 'Feu' },
|
||||
ice: { class: 'text-light-blue dark:text-dark-blue', text: 'Glace' },
|
||||
thunder: { class: 'text-light-yellow dark:text-dark-yellow', text: 'Foudre' },
|
||||
earth: { class: 'text-light-orange dark:text-dark-orange', text: 'Terre' },
|
||||
arcana: { class: 'text-light-purple dark:text-dark-purple', text: 'Arcane' },
|
||||
air: { class: 'text-light-green dark:text-dark-green', text: 'Air' },
|
||||
nature: { class: 'text-light-green dark:text-dark-green', text: 'Nature' },
|
||||
light: { class: 'text-light-yellow dark:text-dark-yellow', text: 'Lumière' },
|
||||
psyche: { class: 'text-light-purple dark:text-dark-purple', text: 'Psy' },
|
||||
}
|
||||
export const spellTypeTexts: Record<SpellType, string> = { "instinct": "Instinct", "knowledge": "Savoir", "precision": "Précision", "arts": "Oeuvres" };
|
||||
|
||||
export const CharacterValidation = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
people: z.number().nullable(),
|
||||
level: z.number().min(1).max(20),
|
||||
aspect: z.number().nullable().optional(),
|
||||
notes: z.string().nullable().optional(),
|
||||
health: z.number().default(0),
|
||||
mana: z.number().default(0),
|
||||
training: z.object(MAIN_STATS.reduce((p, v) => {
|
||||
p[v] = z.array(z.tuple([z.number().min(0).max(15), z.number()]));
|
||||
return p;
|
||||
}, {} as Record<MainStat, z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>)),
|
||||
leveling: z.array(z.tuple([z.number().min(1).max(20), z.number()])),
|
||||
abilities: z.object(ABILITIES.reduce((p, v) => {
|
||||
p[v] = z.tuple([z.number(), z.number()]);
|
||||
return p;
|
||||
}, {} as Record<Ability, z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>)).partial(),
|
||||
spells: z.string().array(),
|
||||
modifiers: z.object(MAIN_STATS.reduce((p, v) => {
|
||||
p[v] = z.number();
|
||||
return p;
|
||||
}, {} as Record<MainStat, z.ZodNumber>)).partial(),
|
||||
owner: z.number(),
|
||||
username: z.string().optional(),
|
||||
visibility: z.enum(["public", "private"]),
|
||||
thumbnail: z.any(),
|
||||
})
|
||||
export type Character = {
|
||||
id: number;
|
||||
|
||||
name: string;
|
||||
people?: number;
|
||||
level: number;
|
||||
aspect?: number | null;
|
||||
notes?: string | null;
|
||||
health: number;
|
||||
mana: number;
|
||||
|
||||
training: Record<MainStat, DoubleIndex<TrainingLevel>[]>;
|
||||
leveling: DoubleIndex<Level>[];
|
||||
abilities: Partial<Record<Ability, [number, number]>>; //First is the ability, second is the max increment
|
||||
spells: string[]; //Spell ID
|
||||
modifiers: Partial<Record<MainStat, number>>;
|
||||
|
||||
owner: number;
|
||||
username?: string;
|
||||
visibility: "private" | "public";
|
||||
};
|
||||
export type CharacterValues = {
|
||||
health: number;
|
||||
mana: number;
|
||||
};
|
||||
export type CharacterConfig = {
|
||||
peoples: Race[],
|
||||
training: Record<MainStat, Record<TrainingLevel, TrainingOption[]>>;
|
||||
abilities: Record<Ability, AbilityConfig>;
|
||||
resistances: Record<Resistance, ResistanceConfig>;
|
||||
spells: SpellConfig[];
|
||||
};
|
||||
export type SpellConfig = {
|
||||
id: string;
|
||||
name: string;
|
||||
rank: 1 | 2 | 3;
|
||||
type: SpellType;
|
||||
cost: number;
|
||||
speed: "action" | "reaction" | number;
|
||||
elements: Array<SpellElement>;
|
||||
effect: string;
|
||||
tags?: string[];
|
||||
};
|
||||
export type AbilityConfig = {
|
||||
max: [MainStat, MainStat];
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
export type ResistanceConfig = {
|
||||
name: string;
|
||||
statistic: MainStat;
|
||||
};
|
||||
export type Race = {
|
||||
name: string;
|
||||
description: string;
|
||||
utils: {
|
||||
maxOption: number;
|
||||
};
|
||||
options: Record<Level, RaceOption[]>;
|
||||
};
|
||||
export type RaceOption = {
|
||||
training?: number;
|
||||
health?: number;
|
||||
mana?: number;
|
||||
shaping?: number;
|
||||
modifier?: number;
|
||||
abilities?: number;
|
||||
spellslots?: number;
|
||||
};
|
||||
export type Feature = {
|
||||
text?: string;
|
||||
} & (ActionFeature | ReactionFeature | FreeActionFeature | BonusFeature | MiscFeature);
|
||||
type ActionFeature = {
|
||||
type: "action";
|
||||
cost: 1 | 2 | 3;
|
||||
text: string;
|
||||
};
|
||||
type ReactionFeature = {
|
||||
type: "reaction";
|
||||
text: string;
|
||||
};
|
||||
type FreeActionFeature = {
|
||||
type: "freeaction";
|
||||
text: string;
|
||||
};
|
||||
type BonusFeature = {
|
||||
type: "bonus";
|
||||
action: "add" | "remove" | "set" | "cap";
|
||||
value: number;
|
||||
property: string;
|
||||
};
|
||||
type MiscFeature = {
|
||||
type: "misc";
|
||||
text: string;
|
||||
};
|
||||
export type TrainingOption = {
|
||||
description: Array<{
|
||||
text: string;
|
||||
disposable?: boolean;
|
||||
replaced?: boolean;
|
||||
category?: Category;
|
||||
}>;
|
||||
|
||||
//Automatically calculated by compiler
|
||||
mana?: number;
|
||||
health?: number;
|
||||
speed?: false | number;
|
||||
initiative?: number;
|
||||
mastery?: keyof CompiledCharacter["mastery"];
|
||||
spellrank?: SpellType;
|
||||
defense?: Array<keyof CompiledCharacter["defense"]>;
|
||||
resistance?: [Resistance, "attack" | "defense"][];
|
||||
spell?: string;
|
||||
|
||||
//Used during character creation, not used by compiler
|
||||
modifier?: number;
|
||||
ability?: number;
|
||||
spec?: number;
|
||||
spellslot?: number | MainStat;
|
||||
arts?: number | MainStat;
|
||||
|
||||
features?: Feature[]; //TODO
|
||||
};
|
||||
export type CompiledCharacter = {
|
||||
id: number;
|
||||
owner?: number;
|
||||
username?: string;
|
||||
name: string;
|
||||
health: number;
|
||||
mana: number;
|
||||
race: number;
|
||||
spellslots: number;
|
||||
artslots: number;
|
||||
spellranks: Record<SpellType, 0 | 1 | 2 | 3>;
|
||||
aspect: string;
|
||||
speed: number | false;
|
||||
initiative: number;
|
||||
spells: string[];
|
||||
|
||||
values: CharacterValues,
|
||||
|
||||
defense: {
|
||||
static: number;
|
||||
activeparry: number;
|
||||
activedodge: number;
|
||||
passiveparry: number;
|
||||
passivedodge: number;
|
||||
};
|
||||
|
||||
mastery: {
|
||||
strength: number;
|
||||
dexterity: number;
|
||||
shield: number;
|
||||
armor: number;
|
||||
multiattack: number;
|
||||
magicpower: number;
|
||||
magicspeed: number;
|
||||
magicelement: number;
|
||||
};
|
||||
|
||||
//First is attack, second is defense
|
||||
resistance: Record<Resistance, [number, number]>;
|
||||
|
||||
modifier: Record<MainStat, number>;
|
||||
abilities: Partial<Record<Ability, number>>;
|
||||
level: number;
|
||||
features: Record<Category, string[]>; //Currently: List of training option as text. TODO: Update to a more complex structure later
|
||||
|
||||
notes: string;
|
||||
};
|
||||
Loading…
Reference in New Issue