init
This commit is contained in:
commit
98131c3b78
29 changed files with 2003 additions and 0 deletions
7
src/App.vue
Normal file
7
src/App.vue
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { RouterView } from "vue-router";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView></RouterView>
|
||||
</template>
|
||||
3
src/components/ErrorComponent.vue
Normal file
3
src/components/ErrorComponent.vue
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<p>Error</p>
|
||||
</template>
|
||||
19
src/components/LastAddedEntry.vue
Normal file
19
src/components/LastAddedEntry.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { ReadApi } from "@/services/read-api";
|
||||
import { Console, Effect, pipe } from "effect";
|
||||
|
||||
const lastAddedEntry = await pipe(
|
||||
Effect.andThen(ReadApi, (api: ReadApi) =>
|
||||
pipe(
|
||||
api.getLastAddedEntry(),
|
||||
Effect.tapError(Console.warn),
|
||||
Effect.orElseSucceed(() => null),
|
||||
)),
|
||||
Effect.provide(ReadApi.Default),
|
||||
Effect.runPromise,
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p>hello</p>
|
||||
</template>
|
||||
3
src/components/LoadingComponent.vue
Normal file
3
src/components/LoadingComponent.vue
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<p>loading...</p>
|
||||
</template>
|
||||
22
src/db/constants.ts
Normal file
22
src/db/constants.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
export const MEDIUM_TYPES = {
|
||||
DOCUMENTARY: "documentary",
|
||||
FILM: "film",
|
||||
SERIES: "series",
|
||||
} as const;
|
||||
|
||||
export const DIARY_ENTRY_STATES = {
|
||||
/** Un média abandonné, lors de son acquisition ou de son visionnage. */
|
||||
DROPPED: "dropped",
|
||||
/** Un média souhaité à acquérir. */
|
||||
TO_FIND: "to_find",
|
||||
/** Un média déjà vu et à revoir. */
|
||||
TO_REWATCH: "to_rewatch",
|
||||
/** Un média souhaité mais dont l'état est encore flou. */
|
||||
TO_SORT: "to_sort",
|
||||
/** Un média acquis et à regarder. */
|
||||
TO_WATCH: "to_watch",
|
||||
/** Un média à l'état inconnu (état par défaut). */
|
||||
UNKNOWN: "unknown",
|
||||
/** Un média ayant été regardé au mois une fois. */
|
||||
WATCHED: "watched",
|
||||
} as const;
|
||||
20
src/db/drizzle.config.ts
Normal file
20
src/db/drizzle.config.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { Config } from "drizzle-kit";
|
||||
|
||||
import { loadEnv } from "vite";
|
||||
|
||||
const env = loadEnv("development", process.cwd(), "");
|
||||
const DATABASE_URL = env["DATABASE_URL"];
|
||||
|
||||
// L'URL de la BDD doit être valide.
|
||||
if (!DATABASE_URL || DATABASE_URL === "") {
|
||||
throw new Error("L'URL de la base de données doit être renseignée dans le fichier de variables d'environnement.");
|
||||
}
|
||||
|
||||
const DrizzleKitConfig: Config = {
|
||||
dbCredentials: { url: DATABASE_URL },
|
||||
dialect: "sqlite",
|
||||
out: "./src/db/drizzle",
|
||||
schema: "./src/db/schema.ts",
|
||||
};
|
||||
|
||||
export default DrizzleKitConfig;
|
||||
66
src/db/drizzle/0000_perfect_justice.sql
Normal file
66
src/db/drizzle/0000_perfect_justice.sql
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
CREATE TABLE `art_works` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`medium_type_id` integer,
|
||||
`name` text NOT NULL,
|
||||
`release_date` text(10) NOT NULL,
|
||||
FOREIGN KEY (`medium_type_id`) REFERENCES `medium_types`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `diary_entries` (
|
||||
`art_work_id` integer,
|
||||
`date_created` text(10) NOT NULL,
|
||||
`date_modified` text(10) NOT NULL,
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`state_id` integer,
|
||||
`user_id` integer,
|
||||
FOREIGN KEY (`art_work_id`) REFERENCES `art_works`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`state_id`) REFERENCES `diary_entries_states`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `diary_entries_genres` (
|
||||
`art_work_id` integer,
|
||||
`genre_id` integer,
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
FOREIGN KEY (`art_work_id`) REFERENCES `art_works`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`genre_id`) REFERENCES `genres`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `diary_entries_states` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`state` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `diary_entries_states_state_unique` ON `diary_entries_states` (`state`);--> statement-breakpoint
|
||||
CREATE TABLE `genres` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`slug` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `genres_name_unique` ON `genres` (`name`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `genres_slug_unique` ON `genres` (`slug`);--> statement-breakpoint
|
||||
CREATE TABLE `medium_types` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`slug` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `medium_types_name_unique` ON `medium_types` (`name`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `medium_types_slug_unique` ON `medium_types` (`slug`);--> statement-breakpoint
|
||||
CREATE TABLE `users` (
|
||||
`email` text NOT NULL,
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_name_unique` ON `users` (`name`);--> statement-breakpoint
|
||||
CREATE TABLE `viewings` (
|
||||
`art_work_id` integer,
|
||||
`date` text(10) NOT NULL,
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`user_id` integer,
|
||||
FOREIGN KEY (`art_work_id`) REFERENCES `art_works`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
277
src/db/drizzle/meta/0000_snapshot.json
Normal file
277
src/db/drizzle/meta/0000_snapshot.json
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "7f64c80e-6fd1-4f8c-859c-dac137206238",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"art_works": {
|
||||
"name": "art_works",
|
||||
"columns": {
|
||||
"id": { "name": "id", "type": "integer", "primaryKey": true, "notNull": true, "autoincrement": true },
|
||||
"medium_type_id": {
|
||||
"name": "medium_type_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": { "name": "name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false },
|
||||
"release_date": {
|
||||
"name": "release_date",
|
||||
"type": "text(10)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"art_works_medium_type_id_medium_types_id_fk": {
|
||||
"name": "art_works_medium_type_id_medium_types_id_fk",
|
||||
"tableFrom": "art_works",
|
||||
"tableTo": "medium_types",
|
||||
"columnsFrom": ["medium_type_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"diary_entries": {
|
||||
"name": "diary_entries",
|
||||
"columns": {
|
||||
"art_work_id": {
|
||||
"name": "art_work_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"date_created": {
|
||||
"name": "date_created",
|
||||
"type": "text(10)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"date_modified": {
|
||||
"name": "date_modified",
|
||||
"type": "text(10)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"id": { "name": "id", "type": "integer", "primaryKey": true, "notNull": true, "autoincrement": true },
|
||||
"state_id": {
|
||||
"name": "state_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"diary_entries_art_work_id_art_works_id_fk": {
|
||||
"name": "diary_entries_art_work_id_art_works_id_fk",
|
||||
"tableFrom": "diary_entries",
|
||||
"tableTo": "art_works",
|
||||
"columnsFrom": ["art_work_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"diary_entries_state_id_diary_entries_states_id_fk": {
|
||||
"name": "diary_entries_state_id_diary_entries_states_id_fk",
|
||||
"tableFrom": "diary_entries",
|
||||
"tableTo": "diary_entries_states",
|
||||
"columnsFrom": ["state_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"diary_entries_user_id_users_id_fk": {
|
||||
"name": "diary_entries_user_id_users_id_fk",
|
||||
"tableFrom": "diary_entries",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"diary_entries_genres": {
|
||||
"name": "diary_entries_genres",
|
||||
"columns": {
|
||||
"art_work_id": {
|
||||
"name": "art_work_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"genre_id": {
|
||||
"name": "genre_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"id": { "name": "id", "type": "integer", "primaryKey": true, "notNull": true, "autoincrement": true }
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"diary_entries_genres_art_work_id_art_works_id_fk": {
|
||||
"name": "diary_entries_genres_art_work_id_art_works_id_fk",
|
||||
"tableFrom": "diary_entries_genres",
|
||||
"tableTo": "art_works",
|
||||
"columnsFrom": ["art_work_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"diary_entries_genres_genre_id_genres_id_fk": {
|
||||
"name": "diary_entries_genres_genre_id_genres_id_fk",
|
||||
"tableFrom": "diary_entries_genres",
|
||||
"tableTo": "genres",
|
||||
"columnsFrom": ["genre_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"diary_entries_states": {
|
||||
"name": "diary_entries_states",
|
||||
"columns": {
|
||||
"id": { "name": "id", "type": "integer", "primaryKey": true, "notNull": true, "autoincrement": true },
|
||||
"state": { "name": "state", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }
|
||||
},
|
||||
"indexes": {
|
||||
"diary_entries_states_state_unique": {
|
||||
"name": "diary_entries_states_state_unique",
|
||||
"columns": ["state"],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"genres": {
|
||||
"name": "genres",
|
||||
"columns": {
|
||||
"id": { "name": "id", "type": "integer", "primaryKey": true, "notNull": true, "autoincrement": true },
|
||||
"name": { "name": "name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false },
|
||||
"slug": { "name": "slug", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }
|
||||
},
|
||||
"indexes": {
|
||||
"genres_name_unique": { "name": "genres_name_unique", "columns": ["name"], "isUnique": true },
|
||||
"genres_slug_unique": { "name": "genres_slug_unique", "columns": ["slug"], "isUnique": true }
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"medium_types": {
|
||||
"name": "medium_types",
|
||||
"columns": {
|
||||
"id": { "name": "id", "type": "integer", "primaryKey": true, "notNull": true, "autoincrement": true },
|
||||
"name": { "name": "name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false },
|
||||
"slug": { "name": "slug", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }
|
||||
},
|
||||
"indexes": {
|
||||
"medium_types_name_unique": { "name": "medium_types_name_unique", "columns": ["name"], "isUnique": true },
|
||||
"medium_types_slug_unique": { "name": "medium_types_slug_unique", "columns": ["slug"], "isUnique": true }
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"email": { "name": "email", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false },
|
||||
"id": { "name": "id", "type": "integer", "primaryKey": true, "notNull": true, "autoincrement": true },
|
||||
"name": { "name": "name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }
|
||||
},
|
||||
"indexes": {
|
||||
"users_email_unique": { "name": "users_email_unique", "columns": ["email"], "isUnique": true },
|
||||
"users_name_unique": { "name": "users_name_unique", "columns": ["name"], "isUnique": true }
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"viewings": {
|
||||
"name": "viewings",
|
||||
"columns": {
|
||||
"art_work_id": {
|
||||
"name": "art_work_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"date": { "name": "date", "type": "text(10)", "primaryKey": false, "notNull": true, "autoincrement": false },
|
||||
"id": { "name": "id", "type": "integer", "primaryKey": true, "notNull": true, "autoincrement": true },
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"viewings_art_work_id_art_works_id_fk": {
|
||||
"name": "viewings_art_work_id_art_works_id_fk",
|
||||
"tableFrom": "viewings",
|
||||
"tableTo": "art_works",
|
||||
"columnsFrom": ["art_work_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"viewings_user_id_users_id_fk": {
|
||||
"name": "viewings_user_id_users_id_fk",
|
||||
"tableFrom": "viewings",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": { "schemas": {}, "tables": {}, "columns": {} },
|
||||
"internal": { "indexes": {} }
|
||||
}
|
||||
5
src/db/drizzle/meta/_journal.json
Normal file
5
src/db/drizzle/meta/_journal.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [{ "idx": 0, "version": "6", "when": 1739953332871, "tag": "0000_perfect_justice", "breakpoints": true }]
|
||||
}
|
||||
76
src/db/schemas.ts
Normal file
76
src/db/schemas.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import type { Values } from "@/libs/utils/types";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
import type { AnySQLiteColumn } from "drizzle-orm/sqlite-core";
|
||||
|
||||
import * as t from "drizzle-orm/sqlite-core";
|
||||
import { sqliteTable as table } from "drizzle-orm/sqlite-core";
|
||||
|
||||
import type { DIARY_ENTRY_STATES, MEDIUM_TYPES } from "./constants";
|
||||
|
||||
/**
|
||||
* Configuration de la BDD SQLite de l'application.
|
||||
*
|
||||
* Assumer ici que toute colonne :
|
||||
* - non explicitement définie comme optionnelle est obligatoire ;
|
||||
* - non explicitement définie comme unique peut contenir plusieurs fois la même valeur ;
|
||||
*/
|
||||
|
||||
export const Users = table("users", {
|
||||
email: t.text("email").notNull().unique(),
|
||||
id: t.integer("id").primaryKey({ autoIncrement: true }),
|
||||
name: t.text("name").notNull().unique(),
|
||||
});
|
||||
export type User = InferSelectModel<typeof Users>;
|
||||
|
||||
export const MediumTypes = table("medium_types", {
|
||||
id: t.integer("id").primaryKey({ autoIncrement: true }),
|
||||
name: t.text("name").notNull().unique(),
|
||||
slug: t.text("slug").$type<Values<typeof MEDIUM_TYPES>>().notNull().unique(),
|
||||
});
|
||||
export type MediumType = InferSelectModel<typeof MediumTypes>;
|
||||
|
||||
export const ArtWorks = table("art_works", {
|
||||
id: t.integer("id").primaryKey({ autoIncrement: true }),
|
||||
mediumTypeId: t.integer("medium_type_id").references((): AnySQLiteColumn => MediumTypes.id),
|
||||
name: t.text("name").notNull(),
|
||||
releaseDate: t.text("release_date", { length: 10 }).notNull(),
|
||||
});
|
||||
export type ArtWork = InferSelectModel<typeof ArtWorks>;
|
||||
|
||||
export const Genres = table("genres", {
|
||||
id: t.integer("id").primaryKey({ autoIncrement: true }),
|
||||
name: t.text("name").notNull().unique(),
|
||||
slug: t.text("slug").notNull().unique(),
|
||||
});
|
||||
export type Genre = InferSelectModel<typeof Genres>;
|
||||
|
||||
export const DiaryEntries = table("diary_entries", {
|
||||
artWorkId: t.integer("art_work_id").references((): AnySQLiteColumn => ArtWorks.id),
|
||||
dateCreated: t.text("date_created", { length: 10 }).notNull(),
|
||||
dateModified: t.text("date_modified", { length: 10 }).notNull(),
|
||||
id: t.integer("id").primaryKey({ autoIncrement: true }),
|
||||
stateId: t.integer("state_id").references((): AnySQLiteColumn => DiaryEntriesStates.id),
|
||||
userId: t.integer("user_id").references((): AnySQLiteColumn => Users.id),
|
||||
});
|
||||
export type DiaryEntry = InferSelectModel<typeof DiaryEntries>;
|
||||
|
||||
export const Viewings = table("viewings", {
|
||||
artWorkId: t.integer("art_work_id").references((): AnySQLiteColumn => ArtWorks.id),
|
||||
date: t.text("date", { length: 10 }).notNull(),
|
||||
id: t.integer("id").primaryKey({ autoIncrement: true }),
|
||||
userId: t.integer("user_id").references((): AnySQLiteColumn => Users.id),
|
||||
});
|
||||
export type Viewing = InferSelectModel<typeof Viewings>;
|
||||
|
||||
export const DiaryEntriesGenres = table("diary_entries_genres", {
|
||||
artWorkId: t.integer("art_work_id").references((): AnySQLiteColumn => ArtWorks.id),
|
||||
genreId: t.integer("genre_id").references((): AnySQLiteColumn => Genres.id),
|
||||
id: t.integer("id").primaryKey({ autoIncrement: true }),
|
||||
});
|
||||
export type DiaryEntryGenre = InferSelectModel<typeof DiaryEntriesGenres>;
|
||||
|
||||
export const DiaryEntriesStates = table("diary_entries_states", {
|
||||
id: t.integer("id").primaryKey({ autoIncrement: true }),
|
||||
state: t.text("state").$type<Values<typeof DIARY_ENTRY_STATES>>().notNull().unique(),
|
||||
});
|
||||
export type DiaryEntryState = InferSelectModel<typeof DiaryEntriesStates>;
|
||||
42
src/libs/apis/tmdb/schemas.ts
Normal file
42
src/libs/apis/tmdb/schemas.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { Schema } from "effect";
|
||||
|
||||
export class TmdbMovieSearchQueryParams extends Schema.Class<TmdbMovieSearchQueryParams>("TmdbMovieSearchArgs")({
|
||||
include_adult: Schema.Boolean.pipe(
|
||||
Schema.propertySignature,
|
||||
Schema.withConstructorDefault(() => false),
|
||||
),
|
||||
language: Schema.NonEmptyString.pipe(
|
||||
Schema.propertySignature,
|
||||
Schema.withConstructorDefault(() => "en-US"),
|
||||
),
|
||||
page: Schema.NonNegativeInt.pipe(
|
||||
Schema.propertySignature,
|
||||
Schema.withConstructorDefault(() => 1),
|
||||
),
|
||||
primary_release_year: Schema.NonEmptyString.pipe(Schema.length(4), Schema.optional),
|
||||
query: Schema.NonEmptyString,
|
||||
region: Schema.NonEmptyString.pipe(Schema.propertySignature, Schema.withConstructorDefault(() => "fr")),
|
||||
year: Schema.NonEmptyString.pipe(Schema.length(4), Schema.optional),
|
||||
}) {}
|
||||
|
||||
export class TmdbMovieSearchResponse extends Schema.Class<TmdbMovieSearchResponse>("TmdbMovieSearchResponse")({
|
||||
page: Schema.NonNegativeInt,
|
||||
results: Schema.Array(Schema.Struct({
|
||||
adult: Schema.Boolean,
|
||||
backdrop_path: Schema.Union(Schema.String, Schema.Null),
|
||||
genre_ids: Schema.Array(Schema.NonNegativeInt),
|
||||
id: Schema.NonNegativeInt,
|
||||
original_language: Schema.String,
|
||||
original_title: Schema.String,
|
||||
overview: Schema.String,
|
||||
popularity: Schema.Number,
|
||||
poster_path: Schema.Union(Schema.String, Schema.Null),
|
||||
release_date: Schema.NonEmptyString.pipe(Schema.length(10)),
|
||||
title: Schema.String,
|
||||
video: Schema.Boolean,
|
||||
vote_average: Schema.Number,
|
||||
vote_count: Schema.NonNegativeInt,
|
||||
})),
|
||||
total_pages: Schema.NonNegativeInt,
|
||||
total_results: Schema.NonNegativeInt,
|
||||
}) {}
|
||||
15
src/libs/utils/effects.ts
Normal file
15
src/libs/utils/effects.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Array as Arr, Effect, pipe } from "effect";
|
||||
|
||||
/**
|
||||
* Effet qui soit retourne le premier membre d'un tableau, soit retourne le résultat une fonction de secours s'il est vide.
|
||||
* @param orFail La fonction de secours à exécuter.
|
||||
* @returns Le premier membre du tableau ou le résultat de la fonction de secours.
|
||||
*/
|
||||
export const singleResultOrFail = <A, E>(orFail: () => E) =>
|
||||
Effect.flatMap((results: A[]) =>
|
||||
pipe(
|
||||
results,
|
||||
Arr.head,
|
||||
Effect.mapError(_ => orFail()),
|
||||
)
|
||||
);
|
||||
2
src/libs/utils/types.d.ts
vendored
Normal file
2
src/libs/utils/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/** Union de types des valeurs d'un Objet. */
|
||||
export type Values<T extends Record<string, unknown>> = T[keyof T];
|
||||
11
src/main.ts
Normal file
11
src/main.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { createPinia } from "pinia";
|
||||
import { createApp } from "vue";
|
||||
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
app.mount("#app");
|
||||
15
src/router/index.ts
Normal file
15
src/router/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import HomeView from "@/views/HomeView.vue";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
component: HomeView,
|
||||
name: "home",
|
||||
path: "/",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default router;
|
||||
43
src/services/db.ts
Normal file
43
src/services/db.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { drizzle } from "drizzle-orm/sqlite-proxy";
|
||||
import { Data, Effect } from "effect";
|
||||
import { SQLocalDrizzle } from "sqlocal/drizzle";
|
||||
|
||||
class LocalSqliteError extends Data.TaggedError("LocalSqliteError")<{ cause: unknown }> {
|
||||
}
|
||||
|
||||
export class LocalSqlite extends Effect.Service<LocalSqlite>()("LocalSqlite", {
|
||||
effect: Effect.gen(function*() {
|
||||
const client = yield* Effect.try({
|
||||
catch: (error: unknown) => new LocalSqliteError({ cause: error }),
|
||||
try: () =>
|
||||
new SQLocalDrizzle({
|
||||
databasePath: "database.sqlite3",
|
||||
onInit: sql => {
|
||||
return [
|
||||
sql`PRAGMA busy_timeout = 5000;`,
|
||||
sql`PRAGMA cache_size = 1000000000;`,
|
||||
sql`PRAGMA foreign_key_check;`,
|
||||
sql`PRAGMA foreign_keys = true;`,
|
||||
sql`PRAGMA integrity_check;`,
|
||||
sql`PRAGMA journal_mode = WAL;`,
|
||||
sql`PRAGMA page_size = 1024;`,
|
||||
sql`PRAGMA synchronous = NORMAL;`,
|
||||
sql`PRAGMA foreign_key_check;`,
|
||||
sql`PRAGMA temp_story = MEMORY;`,
|
||||
sql`VACUUM;`,
|
||||
];
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const orm = drizzle(client.driver, client.batchDriver);
|
||||
|
||||
const query = <R>(execute: (_: typeof orm) => Promise<R>) =>
|
||||
Effect.tryPromise({
|
||||
catch: (error: unknown) => new LocalSqliteError({ cause: error }),
|
||||
try: () => execute(orm),
|
||||
});
|
||||
|
||||
return { client, orm, query };
|
||||
}),
|
||||
}) {}
|
||||
22
src/services/read-api.ts
Normal file
22
src/services/read-api.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { DiaryEntries } from "@/db/schemas";
|
||||
import { singleResultOrFail } from "@/libs/utils/effects";
|
||||
import { desc } from "drizzle-orm";
|
||||
import { Data, Effect } from "effect";
|
||||
|
||||
import { LocalSqlite } from "./db";
|
||||
|
||||
class ReadApiError extends Data.TaggedError("ReadApiError")<{ cause: unknown }> {}
|
||||
|
||||
export class ReadApi extends Effect.Service<ReadApi>()("ReadApi", {
|
||||
dependencies: [LocalSqlite.Default],
|
||||
effect: Effect.gen(function*() {
|
||||
const { query } = yield* LocalSqlite;
|
||||
|
||||
return {
|
||||
getLastAddedEntry: () =>
|
||||
query(_ => _.select().from(DiaryEntries).limit(1).orderBy(desc(DiaryEntries.dateCreated))).pipe(
|
||||
singleResultOrFail(() => new ReadApiError({ cause: "Aucune entrée n'a encore été ajoutée." })),
|
||||
),
|
||||
};
|
||||
}),
|
||||
}) {}
|
||||
20
src/views/HomeView.vue
Normal file
20
src/views/HomeView.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, Suspense } from "vue";
|
||||
|
||||
const AsyncComp = defineAsyncComponent({
|
||||
delay: 0,
|
||||
loader: () => import("@/components/LastAddedEntry.vue"),
|
||||
timeout: 3000,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<Suspense>
|
||||
<AsyncComp></AsyncComp>
|
||||
<template #fallback>
|
||||
Loading...
|
||||
</template>
|
||||
</Suspense>
|
||||
</main>
|
||||
</template>
|
||||
Loading…
Add table
Add a link
Reference in a new issue