diff --git a/package.json b/package.json index 6ef48ab..082a049 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@sveltejs/adapter-static": "^3.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/jssha": "^3.0.0", "@types/phoenix": "^1.6.0", "@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/parser": "^5.60.0", @@ -54,6 +55,7 @@ "hololive-nick-gen": "^1.0.1", "jssha": "^3.3.0", "phoenix": "^1.7.6", - "qr-scanner": "^1.4.2" + "qr-scanner": "^1.4.2", + "svelte-persisted-store": "^0.11.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 879dc92..b60c267 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: qr-scanner: specifier: ^1.4.2 version: 1.4.2 + svelte-persisted-store: + specifier: ^0.11.0 + version: 0.11.0(svelte@4.2.15) devDependencies: '@steeze-ui/heroicons': specifier: ^2.2.2 @@ -45,6 +48,9 @@ importers: '@sveltejs/vite-plugin-svelte': specifier: ^3.0.0 version: 3.1.0(svelte@4.2.15)(vite@5.2.11(@types/node@20.3.1)) + '@types/jssha': + specifier: ^3.0.0 + version: 3.0.0 '@types/phoenix': specifier: ^1.6.0 version: 1.6.0 @@ -511,6 +517,10 @@ packages: '@types/json-schema@7.0.12': resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + '@types/jssha@3.0.0': + resolution: {integrity: sha512-EfJ5RHBfB/XVl9rg12W6u6on3jEGQXYqIHlEja1/tkn2PWJg+Kz0RjMRuKmoWmDCZp5cz0jlS++XslTpRUFNvQ==} + deprecated: This is a stub types definition. jssha provides its own type definitions, so you do not need this installed. + '@types/node@20.3.1': resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} @@ -1661,6 +1671,12 @@ packages: peerDependencies: svelte: ^3.19.0 || ^4.0.0 + svelte-persisted-store@0.11.0: + resolution: {integrity: sha512-9RgJ5DrawGyyfK22A80cfu8Jose3CV8YjEZKz9Tn94rQ0tWyEmYr+XI+wrVF6wjRbW99JMDSVcFRiM3XzVJj/w==} + engines: {node: '>=0.14'} + peerDependencies: + svelte: ^3.48.0 || ^4.0.0 || ^5.0.0-next.0 + svelte-preprocess@5.0.4: resolution: {integrity: sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==} engines: {node: '>= 14.10.0'} @@ -2256,6 +2272,10 @@ snapshots: '@types/json-schema@7.0.12': {} + '@types/jssha@3.0.0': + dependencies: + jssha: 3.3.0 + '@types/node@20.3.1': {} '@types/offscreencanvas@2019.7.0': {} @@ -3389,6 +3409,10 @@ snapshots: dependencies: svelte: 4.2.15 + svelte-persisted-store@0.11.0(svelte@4.2.15): + dependencies: + svelte: 4.2.15 + svelte-preprocess@5.0.4(postcss-load-config@4.0.1(postcss@8.4.24))(postcss@8.4.24)(svelte@4.2.15)(typescript@5.1.3): dependencies: '@types/pug': 2.0.6 diff --git a/src/const.ts b/src/const.ts index 9c18561..559f067 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,133 +1,13 @@ import { dev } from '$app/environment'; -import type { Stamp } from './custom'; - -import asulNuiProfile from '$lib/assets/asulnui-profile.jpg'; -import cyclicProfilePic from '$lib/assets/cyclic-redundancy-profile.png'; -import erizuProfilePic from '$lib/assets/erizu-profile.jpg'; -import hasukeProfilePic from '$lib/assets/hasuke-profile.jpg'; -import hynoriProfile from '$lib/assets/hynori-profile.png'; -import ioeaProfile from '$lib/assets/ioea-profile.jpg'; -import juweiProfilePic from '$lib/assets/juwei-profile.jpg'; -import maruminProfile from '$lib/assets/marumin-profile.jpg'; -import moriProfilePic from '$lib/assets/morinohitos-profile.jpg'; -import saviProfilePic from '$lib/assets/savi-profile.jpg'; /** * @file This file contains all the constants used in the application. */ -// Stamp ralley partner stamp information. -// Each hash is the SHA1 hash of the stamp UUID token. -export const expectedStamps: Stamp[] = [ - { - hash: '37d895725ad8aa8bba87a139710e909b46cb753e', - id: 1, - name: 'Hasuke はすけ 4C16', - description: ` - Digital Artist & V-Tuber | 🇩🇪🇺🇸🇯🇵 | -| Graphic Art | Illustration | Gamedev | -Certified Hololive Simp -Modell & Banner by me.`, - externalURL: 'https://webapp.dokomi.de/explore/c/108511', - imageURL: hasukeProfilePic - }, - { - id: 2, - name: 'Kagura Nana 3B18', - hash: '65f21aa01e5be625463340fdebf5eac8b636264f', - description: ` - Natsume Eri, Eretto and Nana Kaguraaa - `, - externalURL: 'https://webapp.dokomi.de/explore/c/109092', - imageURL: ioeaProfile - }, - { - id: 3, - name: 'marumin 3C21', - hash: '0cee71fe8e4834108709bbc3986517fc1bb82175', - description: ` - ♡ I'm Maru and I draw!! | DM for commission info || email: amarururun@gmail.com || 🍃🌿🍀rambles -@marusleeps - || https://marustore.storenvy.com - `, - externalURL: 'https://webapp.dokomi.de/explore/c/107001', - imageURL: maruminProfile - }, - { - id: 4, - name: 'hynori🌸 3G94', - hash: '78d2cb5917a477bdaa73e743e2e71760b72f30c7', - description: ` - phi 🌟 she/her 🌟 game art student 🌟 OC, DnD, FFXIV, genshin 🌟 I love alphinaud leveilleur 🌟 comms CLOSED 🌟 email hynorin*gmail*com GER&ENG OK! 日本語まだまだです - `, - externalURL: 'https://webapp.dokomi.de/explore/c/107044', - imageURL: hynoriProfile - }, - { - id: 5, - name: 'Mori @ 3M34', - hash: '76a12efd6c61d78ecf46c820c4210ef9eaceda34', - description: 'Artist into: Anime|VTubers|Games', - externalURL: 'https://webapp.dokomi.de/explore/c/108058', - imageURL: moriProfilePic - }, - { - id: 6, - name: 'SAVI✿サビ M701', - hash: '26f34a24468d310ae469fcde15fef83ed9d3297d', - description: ` - I'm a pink ball who simps for cute girls.. and draws…sometimes (2434) 💓Eng/Рус/日本語/Ger OK💓 business: savi.chan.commissions@gmail(.)com📩info in carrd`, - externalURL: 'https://webapp.dokomi.de/explore/c/107468', - imageURL: saviProfilePic - }, - - { - id: 7, - name: 'CYCLIC★REDUNDANCY 3J48', - hash: '547b3527f079fd616ccd420d242e06092efc2e1b', - description: ` - Doujin 同人 Circle 🎨▪️Account manager 担当者: -@Ninamo_lcr -▪️Online stores 通販 ➡️ See pinned tweet 🛒📌▪️ES/EN/日本語👌`, - externalURL: 'https://webapp.dokomi.de/explore/c/107091', - imageURL: cyclicProfilePic - }, - { - id: 8, - name: 'Erizu 3J19', - hash: '31db5fff1842de328bf801624d6c6a7a368849e2', - description: 'Freelance Illustrator | Live2d Artist/Rigger | Pixel Art Apprentice', - externalURL: 'https://webapp.dokomi.de/explore/c/108335', - imageURL: erizuProfilePic - }, - { - id: 9, - name: 'juwei 🌷🌆 3L53', - hash: 'be94be5fe0c93e5c74a6b62f500b66954eca9ed2', - description: ` - 【artist and tea connoisseur】commissions: http://vgen.co/juwei • stream: http://twitch.tv/juwei_ • support: http://ko-fi.com/juwei • alt: -@jujujuwei - ✨ h: -@pnkkr4mune`, - externalURL: 'https://webapp.dokomi.de/explore/c/107557', - imageURL: juweiProfilePic - }, - { - id: 10, - name: 'Asulnui 3H03', - hash: '6fdfc8bfb68f0c3df42bf6d98064f2a7b058e4e5', - description: ` - Robo doggo⚡🐾 Vtuber training arc. VArtist 🖊️ Art tag #Inuink -ESP/ENG/日本語 OK! -`, - externalURL: 'https://webapp.dokomi.de/explore/c/107086', - imageURL: asulNuiProfile - } -]; - export const socketServerURL = dev ? 'ws://localhost:4000/socket' : 'wss://api.hololivefanbooth.com/socket'; + export const apiServerURL = dev ? 'http://localhost:4000/api/json' : 'https://api.hololivefanbooth.com/api/json'; diff --git a/src/crypto.spec.ts b/src/crypto.spec.ts index 450d7c6..de9bd93 100644 --- a/src/crypto.spec.ts +++ b/src/crypto.spec.ts @@ -1,6 +1,6 @@ // crypto.spec.ts import { test } from 'vitest'; -import { calculateTokenChecksum, sha1 } from './crypto'; // adjust the path if necessary +import { calculateTokenChecksum, sha1, sha256 } from './crypto'; // adjust the path if necessary test('sha1 hashes correctly', () => { const input = 'Hello, world!'; @@ -12,6 +12,16 @@ test('sha1 hashes correctly', () => { } }); +test('sha256 hashes correctly', () => { + const input = 'Hololive saiko!'; + const output = sha256(input); + const expected = '1116a12cc9951753485ffc7290094cdd649b67b4f5df7bfd514024b58fad661e'; // SHA-256 hash of 'Hololive saiko!' + + if (output !== expected) { + throw new Error(`Expected ${expected}, but got ${output}`); + } +}); + test('calculateTokenChecksum hashes correctly', () => { const tokens = ['token1', 'token2', 'token3']; const output = calculateTokenChecksum(tokens); diff --git a/src/crypto.ts b/src/crypto.ts index 1b93ffa..3720541 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -1,9 +1,9 @@ import jsSHA from 'jssha'; /** - * - * @param input a string to be hashed - * @returns a SHA-1 hash of the input + * Calculates the SHA-1 hash of a given input string. + * @param input - The string to be hashed. + * @returns The SHA-1 hash of the input string. */ export function sha1(input: string): string { const shaObj = new jsSHA('SHA-1', 'TEXT'); @@ -12,9 +12,20 @@ export function sha1(input: string): string { } /** - * Calculates the checksum of a string of tokens by concatenating them and hashing the result using sha-256. - * @param tokens an array of tokens to be hashed - * @returns a sha-256 hash of the concatenated tokens + * Calculates the SHA-256 hash of a given input string. + * @param input - The string to be hashed. + * @returns The SHA-256 hash of the input string. + */ +export function sha256(input: string): string { + const shaObj = new jsSHA('SHA-256', 'TEXT'); + shaObj.update(input); + return shaObj.getHash('HEX'); +} + +/** + * Calculates the checksum of an array of tokens by concatenating them and hashing the result using SHA-256. + * @param tokens - An array of tokens to be hashed. + * @returns The SHA-256 hash of the concatenated tokens. */ export function calculateTokenChecksum(tokens: string[]): string { const shaObj = new jsSHA('SHA-256', 'TEXT'); diff --git a/src/custom.ts b/src/custom.ts index 3f321da..9fb3ba1 100644 --- a/src/custom.ts +++ b/src/custom.ts @@ -1,12 +1,3 @@ -export type Stamp = { - id: number; - name: string; - hash: string; - description: string; - externalURL?: string; - imageURL?: string; -}; - export const TOAST_TYPE = { SUCCESS: 'SUCCESS', ERROR: 'ERROR' diff --git a/src/lib/assets/asulnui-profile.jpg b/src/lib/assets/asulnui-profile.jpg deleted file mode 100644 index f4df010..0000000 Binary files a/src/lib/assets/asulnui-profile.jpg and /dev/null differ diff --git a/src/lib/assets/cyclic-redundancy-profile.png b/src/lib/assets/cyclic-redundancy-profile.png deleted file mode 100644 index 663821e..0000000 Binary files a/src/lib/assets/cyclic-redundancy-profile.png and /dev/null differ diff --git a/src/lib/assets/erizu-profile.jpg b/src/lib/assets/erizu-profile.jpg deleted file mode 100644 index 9aebae3..0000000 Binary files a/src/lib/assets/erizu-profile.jpg and /dev/null differ diff --git a/src/lib/assets/hasuke-profile.jpg b/src/lib/assets/hasuke-profile.jpg deleted file mode 100644 index dbe4a5d..0000000 Binary files a/src/lib/assets/hasuke-profile.jpg and /dev/null differ diff --git a/src/lib/assets/hynori-profile.png b/src/lib/assets/hynori-profile.png deleted file mode 100644 index fbbb2a5..0000000 Binary files a/src/lib/assets/hynori-profile.png and /dev/null differ diff --git a/src/lib/assets/ioea-profile.jpg b/src/lib/assets/ioea-profile.jpg deleted file mode 100644 index 30126d0..0000000 Binary files a/src/lib/assets/ioea-profile.jpg and /dev/null differ diff --git a/src/lib/assets/juwei-profile.jpg b/src/lib/assets/juwei-profile.jpg deleted file mode 100644 index 61c2300..0000000 Binary files a/src/lib/assets/juwei-profile.jpg and /dev/null differ diff --git a/src/lib/assets/marumin-profile.jpg b/src/lib/assets/marumin-profile.jpg deleted file mode 100644 index a0d5986..0000000 Binary files a/src/lib/assets/marumin-profile.jpg and /dev/null differ diff --git a/src/lib/assets/morinohitos-profile.jpg b/src/lib/assets/morinohitos-profile.jpg deleted file mode 100644 index 977076a..0000000 Binary files a/src/lib/assets/morinohitos-profile.jpg and /dev/null differ diff --git a/src/lib/assets/savi-profile.jpg b/src/lib/assets/savi-profile.jpg deleted file mode 100644 index 9f826d4..0000000 Binary files a/src/lib/assets/savi-profile.jpg and /dev/null differ diff --git a/src/lib/components/StampSheet.svelte b/src/lib/components/StampSheet.svelte index c16dd3a..50f3356 100644 --- a/src/lib/components/StampSheet.svelte +++ b/src/lib/components/StampSheet.svelte @@ -2,15 +2,17 @@ import { fade, fly } from 'svelte/transition'; import { cubicOut } from 'svelte/easing'; import StampComponent from '$lib/components/Stamp.svelte'; - import type { Stamp } from '../../custom'; + import type {Tables} from '$lib/database.types'; import { onMount } from 'svelte'; - import { expectedStamps } from '../../const'; + import HolomemGacha from './HolomemGacha.svelte'; import RoundScanButton from './RoundScanButton.svelte'; + import { collectedStamps } from '$lib/stores/stamps'; + import { get } from 'svelte/store'; - export let stamps: Stamp[] = []; + export let stamps: Tables<'stamps'>[] = []; - let isStampCollected = function (_stamp: Stamp) { + let isStampCollected = function (_stamp: Tables<'stamps'>) { return false; }; @@ -20,15 +22,16 @@ let isQuestCompleted = false; // Can only be true if all stamps were collected function isAllStampsCollected() { - return expectedStamps.every(isStampCollected); + return stamps.every(isStampCollected); } const delay = 500; // synchronized fade in delay const minTouchTime = 1000; // minimum touch time in milliseconds, how long the stub of the stamp sheet should be touched onMount(() => { - isStampCollected = function (stamp: Stamp) { - return localStorage.getItem(stamp.hash) !== null; + isStampCollected = function (stamp: Tables<'stamps'>) { + // return localStorage.getItem(stamp.hash) !== null; + return get(collectedStamps)[stamp.hash]; }; tearStampSheet = function () { @@ -121,7 +124,7 @@ name={stamp.name} collected={isStampCollected(stamp)} id={stamp.id} - img={stamp.imageURL} + img={stamp.image_url || undefined} /> {/each} diff --git a/src/lib/database.types.ts b/src/lib/database.types.ts new file mode 100644 index 0000000..1b9f743 --- /dev/null +++ b/src/lib/database.types.ts @@ -0,0 +1,506 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export type Database = { + graphql_public: { + Tables: { + [_ in never]: never + } + Views: { + [_ in never]: never + } + Functions: { + graphql: { + Args: { + operationName?: string + query?: string + variables?: Json + extensions?: Json + } + Returns: Json + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + public: { + Tables: { + events: { + Row: { + created_at: string + id: number + metadata: Json | null + name: string + } + Insert: { + created_at?: string + id?: number + metadata?: Json | null + name: string + } + Update: { + created_at?: string + id?: number + metadata?: Json | null + name?: string + } + Relationships: [] + } + stamps: { + Row: { + booth_id: string | null + created_at: string + description: string | null + event_id: number + external_url: string | null + hash: string | null + id: string + image_url: string | null + name: string + nsfw: boolean + } + Insert: { + booth_id?: string | null + created_at?: string + description?: string | null + event_id: number + external_url?: string | null + hash?: string | null + id?: string + image_url?: string | null + name: string + nsfw?: boolean + } + Update: { + booth_id?: string | null + created_at?: string + description?: string | null + event_id?: number + external_url?: string | null + hash?: string | null + id?: string + image_url?: string | null + name?: string + nsfw?: boolean + } + Relationships: [ + { + foreignKeyName: "stamps_event_id_fkey" + columns: ["event_id"] + isOneToOne: false + referencedRelation: "events" + referencedColumns: ["id"] + }, + ] + } + } + Views: { + [_ in never]: never + } + Functions: { + [_ in never]: never + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + storage: { + Tables: { + buckets: { + Row: { + allowed_mime_types: string[] | null + avif_autodetection: boolean | null + created_at: string | null + file_size_limit: number | null + id: string + name: string + owner: string | null + owner_id: string | null + public: boolean | null + updated_at: string | null + } + Insert: { + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id: string + name: string + owner?: string | null + owner_id?: string | null + public?: boolean | null + updated_at?: string | null + } + Update: { + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id?: string + name?: string + owner?: string | null + owner_id?: string | null + public?: boolean | null + updated_at?: string | null + } + Relationships: [] + } + migrations: { + Row: { + executed_at: string | null + hash: string + id: number + name: string + } + Insert: { + executed_at?: string | null + hash: string + id: number + name: string + } + Update: { + executed_at?: string | null + hash?: string + id?: number + name?: string + } + Relationships: [] + } + objects: { + Row: { + bucket_id: string | null + created_at: string | null + id: string + last_accessed_at: string | null + metadata: Json | null + name: string | null + owner: string | null + owner_id: string | null + path_tokens: string[] | null + updated_at: string | null + version: string | null + } + Insert: { + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + owner_id?: string | null + path_tokens?: string[] | null + updated_at?: string | null + version?: string | null + } + Update: { + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + owner_id?: string | null + path_tokens?: string[] | null + updated_at?: string | null + version?: string | null + } + Relationships: [ + { + foreignKeyName: "objects_bucketId_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] + }, + ] + } + s3_multipart_uploads: { + Row: { + bucket_id: string + created_at: string + id: string + in_progress_size: number + key: string + owner_id: string | null + upload_signature: string + version: string + } + Insert: { + bucket_id: string + created_at?: string + id: string + in_progress_size?: number + key: string + owner_id?: string | null + upload_signature: string + version: string + } + Update: { + bucket_id?: string + created_at?: string + id?: string + in_progress_size?: number + key?: string + owner_id?: string | null + upload_signature?: string + version?: string + } + Relationships: [ + { + foreignKeyName: "s3_multipart_uploads_bucket_id_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] + }, + ] + } + s3_multipart_uploads_parts: { + Row: { + bucket_id: string + created_at: string + etag: string + id: string + key: string + owner_id: string | null + part_number: number + size: number + upload_id: string + version: string + } + Insert: { + bucket_id: string + created_at?: string + etag: string + id?: string + key: string + owner_id?: string | null + part_number: number + size?: number + upload_id: string + version: string + } + Update: { + bucket_id?: string + created_at?: string + etag?: string + id?: string + key?: string + owner_id?: string | null + part_number?: number + size?: number + upload_id?: string + version?: string + } + Relationships: [ + { + foreignKeyName: "s3_multipart_uploads_parts_bucket_id_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] + }, + { + foreignKeyName: "s3_multipart_uploads_parts_upload_id_fkey" + columns: ["upload_id"] + isOneToOne: false + referencedRelation: "s3_multipart_uploads" + referencedColumns: ["id"] + }, + ] + } + } + Views: { + [_ in never]: never + } + Functions: { + can_insert_object: { + Args: { + bucketid: string + name: string + owner: string + metadata: Json + } + Returns: undefined + } + extension: { + Args: { + name: string + } + Returns: string + } + filename: { + Args: { + name: string + } + Returns: string + } + foldername: { + Args: { + name: string + } + Returns: string[] + } + get_size_by_bucket: { + Args: Record + Returns: { + size: number + bucket_id: string + }[] + } + list_multipart_uploads_with_delimiter: { + Args: { + bucket_id: string + prefix_param: string + delimiter_param: string + max_keys?: number + next_key_token?: string + next_upload_token?: string + } + Returns: { + key: string + id: string + created_at: string + }[] + } + list_objects_with_delimiter: { + Args: { + bucket_id: string + prefix_param: string + delimiter_param: string + max_keys?: number + start_after?: string + next_token?: string + } + Returns: { + name: string + id: string + metadata: Json + updated_at: string + }[] + } + search: { + Args: { + prefix: string + bucketname: string + limits?: number + levels?: number + offsets?: number + search?: string + sortcolumn?: string + sortorder?: string + } + Returns: { + name: string + id: string + updated_at: string + created_at: string + last_accessed_at: string + metadata: Json + }[] + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } +} + +type PublicSchema = Database[Extract] + +export type Tables< + PublicTableNameOrOptions extends + | keyof (PublicSchema["Tables"] & PublicSchema["Views"]) + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & + PublicSchema["Views"]) + ? (PublicSchema["Tables"] & + PublicSchema["Views"])[PublicTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + PublicTableNameOrOptions extends + | keyof PublicSchema["Tables"] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + PublicTableNameOrOptions extends + | keyof PublicSchema["Tables"] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + PublicEnumNameOrOptions extends + | keyof PublicSchema["Enums"] + | { schema: keyof Database }, + EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = PublicEnumNameOrOptions extends { schema: keyof Database } + ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName] + : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] + ? PublicSchema["Enums"][PublicEnumNameOrOptions] + : never diff --git a/src/lib/stores/stamps.ts b/src/lib/stores/stamps.ts new file mode 100644 index 0000000..3644b1b --- /dev/null +++ b/src/lib/stores/stamps.ts @@ -0,0 +1,4 @@ +import { persisted } from 'svelte-persisted-store'; + +export const collectedStamps = persisted('collected-stamps', {}); +export const expectedStamps = persisted('expected-stamps', {}); diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 0d08196..e5748d6 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -2,6 +2,27 @@ export const csr = true; import type { LayoutLoad } from './$types'; -export const load = (() => { +import { expectedStamps } from '$lib/stores/stamps'; +import { supabase } from '../supabase-client'; + +export const load = (async () => { + await startupTasks(); return {}; }) satisfies LayoutLoad; + +// Startup tasks +async function startupTasks() { + console.log('Running startup tasks...'); + await updateExpectedStamps(); +} + +async function updateExpectedStamps() { + console.log('Fetching stamp data...'); + const { data } = await supabase.from('stamps').select('*'); + const stampsData = data?.reduce((acc, stamp) => { + const { hash, ...rest } = stamp; + acc[hash] = rest; + return acc; + }, {}); + expectedStamps.set(stampsData); +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index c228a58..83407b6 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,7 @@ - + diff --git a/src/routes/+page.ts b/src/routes/+page.ts index 978721a..e232f41 100644 --- a/src/routes/+page.ts +++ b/src/routes/+page.ts @@ -1,10 +1,11 @@ -import { supabase } from '../supabase-client'; +import { expectedStamps } from '$lib/stores/stamps'; +import { get } from 'svelte/store'; import type { PageLoad } from './$types'; export const ssr = false; export const load = (async () => { - const { data } = await supabase.from('stamps').select('*'); + const stamps = Object.values(get(expectedStamps)); - return { stamps: data }; + return { stamps }; }) satisfies PageLoad; diff --git a/src/routes/partner/[id]/+page.svelte b/src/routes/partner/[id]/+page.svelte index d316eea..1257f0b 100644 --- a/src/routes/partner/[id]/+page.svelte +++ b/src/routes/partner/[id]/+page.svelte @@ -1,17 +1,17 @@
- {name + {name

{name}

@@ -30,7 +30,7 @@ {description}

-
Open in DoKomi app { const { id } = params; - const partner = expectedStamps.find((stamp) => stamp.id === Number(id)) as object | undefined; + // const partner = expectedStamps.find((stamp) => stamp.id === Number(id)) as object | undefined; + + // if (!partner) { + // return error(404, 'Not found'); + // } - if (!partner) { - return error(404, 'Not found'); - } + // return { partner }; - return { partner }; + return error(404, 'Not impleneted yet'); }) satisfies PageLoad; diff --git a/src/routes/sc/[id]/+page.ts b/src/routes/sc/[id]/+page.ts new file mode 100644 index 0000000..b702587 --- /dev/null +++ b/src/routes/sc/[id]/+page.ts @@ -0,0 +1,6 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + redirect(301, '/scanner'); +}) satisfies PageLoad; diff --git a/src/routes/scanner/+page.svelte b/src/routes/scanner/+page.svelte index b44682e..22c71ae 100644 --- a/src/routes/scanner/+page.svelte +++ b/src/routes/scanner/+page.svelte @@ -1,24 +1,25 @@