diff --git a/drizzle/0000_flawless_russian.sql b/drizzle/0000_flawless_russian.sql deleted file mode 100644 index 4d39f65..0000000 --- a/drizzle/0000_flawless_russian.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE `users` ( - `id` text, - `name` text -); diff --git a/drizzle/0000_parched_mandroid.sql b/drizzle/0000_parched_mandroid.sql new file mode 100644 index 0000000..0883f52 --- /dev/null +++ b/drizzle/0000_parched_mandroid.sql @@ -0,0 +1,24 @@ +CREATE TABLE `deal` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `title` text, + `description` text, + `isActive` integer +); +--> statement-breakpoint +CREATE TABLE `glamp` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `title` text, + `description` text, + `type` text, + `price` real, + `available_from` text, + `available_to` text, + `adult_capacity` integer, + `child_capacity` integer, + `isLuxury` integer +); +--> statement-breakpoint +CREATE TABLE `users` ( + `id` text, + `name` text +); diff --git a/drizzle/0001_burly_mister_sinister.sql b/drizzle/0001_burly_mister_sinister.sql deleted file mode 100644 index 7448f39..0000000 --- a/drizzle/0001_burly_mister_sinister.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE `deal` ( - `id` text PRIMARY KEY NOT NULL, - `title` text, - `description` text -); diff --git a/drizzle/0002_conscious_rachel_grey.sql b/drizzle/0002_conscious_rachel_grey.sql deleted file mode 100644 index 3229f6f..0000000 --- a/drizzle/0002_conscious_rachel_grey.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE deal ADD `isActive` integer; \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index e093b48..3409c62 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,9 +1,125 @@ { "version": "5", "dialect": "sqlite", - "id": "f8921c19-f0a0-4333-b33e-e2a5b21f8d05", + "id": "47e4144c-cfb4-4322-b044-26e66004cc62", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { + "deal": { + "name": "deal", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "isActive": { + "name": "isActive", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "glamp": { + "name": "glamp", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "price": { + "name": "price", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "available_from": { + "name": "available_from", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "available_to": { + "name": "available_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "adult_capacity": { + "name": "adult_capacity", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "child_capacity": { + "name": "child_capacity", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "isLuxury": { + "name": "isLuxury", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, "users": { "name": "users", "columns": { diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json deleted file mode 100644 index bdae0e7..0000000 --- a/drizzle/meta/0001_snapshot.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "version": "5", - "dialect": "sqlite", - "id": "5ada055f-2a73-4b12-a5cd-8778acb81fd2", - "prevId": "f8921c19-f0a0-4333-b33e-e2a5b21f8d05", - "tables": { - "deal": { - "name": "deal", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json deleted file mode 100644 index 879bfbf..0000000 --- a/drizzle/meta/0002_snapshot.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "version": "5", - "dialect": "sqlite", - "id": "e317365c-23a1-48c6-b50f-51f9763f324b", - "prevId": "5ada055f-2a73-4b12-a5cd-8778acb81fd2", - "tables": { - "deal": { - "name": "deal", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "isActive": { - "name": "isActive", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index fed0eca..66ec7f1 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,22 +5,8 @@ { "idx": 0, "version": "5", - "when": 1703493410904, - "tag": "0000_flawless_russian", - "breakpoints": true - }, - { - "idx": 1, - "version": "5", - "when": 1704553290895, - "tag": "0001_burly_mister_sinister", - "breakpoints": true - }, - { - "idx": 2, - "version": "5", - "when": 1704621273333, - "tag": "0002_conscious_rachel_grey", + "when": 1707588316049, + "tag": "0000_parched_mandroid", "breakpoints": true } ] diff --git a/package-lock.json b/package-lock.json index 2a1263b..2d054eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "@graphql-codegen/typescript": "^4.0.1", "@graphql-codegen/typescript-operations": "^4.0.1", "@graphql-codegen/typescript-react-apollo": "^4.1.0", + "@types/better-sqlite3": "^7.6.9", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", @@ -4948,6 +4949,15 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.9", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.9.tgz", + "integrity": "sha512-FvktcujPDj9XKMJQWFcl2vVl7OdRIqsSRX9b0acWwTmwLK9CF2eqo/FRcmMLNpugKoX/avA6pb7TorDLmpgTnQ==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", diff --git a/package.json b/package.json index 1cf0bc6..2776755 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,11 @@ "lint": "next lint", "codegen": "graphql-codegen", "drizzle-studio": "drizzle-kit studio", - "db-migration": "drizzle-kit generate:sqlite", + "db-migration": "drizzle-kit generate:sqlite --config=drizzle.config.ts", "db-push": "drizzle-kit push:sqlite", "knip": "knip", - "ts-coverage": "typescript-coverage-report" + "ts-coverage": "typescript-coverage-report", + "migrations:drop": "drizzle-kit drop --config=drizzle.config.ts" }, "dependencies": { "@apollo/client": "^3.8.8", @@ -51,6 +52,7 @@ "@graphql-codegen/typescript": "^4.0.1", "@graphql-codegen/typescript-operations": "^4.0.1", "@graphql-codegen/typescript-react-apollo": "^4.1.0", + "@types/better-sqlite3": "^7.6.9", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/sqlite.db b/sqlite.db index 65340c5..2f7cce0 100644 Binary files a/sqlite.db and b/sqlite.db differ diff --git a/src/components/layouts/MainNav.tsx b/src/components/layouts/MainNav.tsx index e1a9989..1a4a026 100644 --- a/src/components/layouts/MainNav.tsx +++ b/src/components/layouts/MainNav.tsx @@ -19,6 +19,7 @@ const ROUTES = { CURSOR_PAGINATION: "/cursor-pagination", OPTIMISTIC_UI: "/optimistic-ui", ERROR_HANDLING: "/error-handling", + REFETCH: "/refetch-changing", }; const MainNav = () => { @@ -50,6 +51,11 @@ const MainNav = () => { Error handling + + + Refetching + + diff --git a/src/db/schema.ts b/src/db/schema.ts index 93b02e1..810dce6 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -1,4 +1,4 @@ -import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core"; export const users = sqliteTable("users", { id: text("id"), @@ -11,3 +11,16 @@ export const deals = sqliteTable("deal", { description: text("description"), isActive: integer("isActive", { mode: "boolean" }), }); + +export const glamps = sqliteTable("glamp", { + id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), + title: text("title"), + description: text("description"), + type: text("type"), + price: real("price"), + availableFrom: text("available_from"), + availableTo: text("available_to"), + adultCapacity: integer("adult_capacity"), + childCapacity: integer("child_capacity"), + isLuxury: integer("isLuxury", { mode: "boolean" }), +}); diff --git a/src/graphql/schema/glamps/glamps.resolver.ts b/src/graphql/schema/glamps/glamps.resolver.ts new file mode 100644 index 0000000..bd38b9b --- /dev/null +++ b/src/graphql/schema/glamps/glamps.resolver.ts @@ -0,0 +1,102 @@ +import { db } from "@/db/db"; +import { glamps as dbGlamps } from "@/db/schema"; +import { and, count, eq, gte, lte } from "drizzle-orm"; +import { Arg, Ctx, Float, Int, Query, Resolver } from "type-graphql"; +import { Glamp, GlampsResponse } from "./glamps"; +import type { MyContext } from "@/pages/api/graphql"; +import { z } from "zod"; + +const DateRangeSchema = z.object({ + from: z.string().datetime({ offset: true }), + to: z.string().datetime({ offset: true }), +}); + +type DateRange = z.infer; + +@Resolver(Glamp) +export class GlampResolver { + @Query(() => [Glamp]) + async glamps(): Promise { + const glamps = db.select().from(dbGlamps).all(); + return glamps; + } + + @Query(() => GlampsResponse) + async searchGlamps( + @Arg("offset", () => Int) offset: number, + @Arg("limit", () => Int) limit: number, + @Ctx() ctx: MyContext, + @Arg("isLuxury", { nullable: true }) isLuxury?: boolean, + @Arg("minPrice", () => Float, { nullable: true }) minPrice?: number, + @Arg("maxPrice", () => Float, { nullable: true }) maxPrice?: number + ): Promise { + let dateRange: DateRange | undefined; + const dateRangeHeader = ctx.req.headers["x-date-range"]; + + if (dateRangeHeader) { + const parsedDateRange = JSON.parse(dateRangeHeader as string); + console.log(parsedDateRange); + const result = DateRangeSchema.safeParse(parsedDateRange); + console.log(result); + if (result.success) { + dateRange = result.data; + } else { + dateRange = undefined; + } + } + + let glampsQueryBuilder = db + .select() + .from(dbGlamps) + .$dynamic() + .offset(offset) + .limit(limit); + + let totalCountQueryBuilder = db + .select({ value: count() }) + .from(dbGlamps) + .$dynamic(); + + /* Strict compliance */ + if (dateRange) { + const { from, to } = dateRange; + glampsQueryBuilder = glampsQueryBuilder.where( + and(gte(dbGlamps.availableFrom, from), lte(dbGlamps.availableTo, to)) + ); + totalCountQueryBuilder = totalCountQueryBuilder.where( + and(gte(dbGlamps.availableFrom, from), lte(dbGlamps.availableTo, to)) + ); + } + + if (isLuxury !== undefined) { + glampsQueryBuilder = glampsQueryBuilder.where( + eq(dbGlamps.isLuxury, isLuxury) + ); + totalCountQueryBuilder = totalCountQueryBuilder.where( + eq(dbGlamps.isLuxury, isLuxury) + ); + } + + if (minPrice && maxPrice) { + glampsQueryBuilder = glampsQueryBuilder.where( + and(gte(dbGlamps.price, minPrice), lte(dbGlamps.price, maxPrice)) + ); + totalCountQueryBuilder = totalCountQueryBuilder.where( + and(gte(dbGlamps.price, minPrice), lte(dbGlamps.price, maxPrice)) + ); + } + + const glamps = await glampsQueryBuilder; + const totalCountResult = await totalCountQueryBuilder; + const totalCount = totalCountResult[0].value; + const hasNextPage = offset + limit < totalCount; + + return { + glamps, + pageInfo: { + hasNextPage, + total: totalCount, + }, + }; + } +} diff --git a/src/graphql/schema/glamps/glamps.ts b/src/graphql/schema/glamps/glamps.ts new file mode 100644 index 0000000..2330fcd --- /dev/null +++ b/src/graphql/schema/glamps/glamps.ts @@ -0,0 +1,52 @@ +import { ObjectType, Field, ID, Float, Int } from "type-graphql"; + +@ObjectType({ description: "Glamp Object" }) +export class Glamp { + @Field(() => ID) + id: number; + + @Field() + title: string; + + @Field({ nullable: true }) + description: string; + + @Field() + type: string; + + @Field(() => Float) + price: number; + + @Field() + availableFrom: string; + + @Field() + availableTo: string; + + @Field(() => Int) + adultCapacity: number; + + @Field(() => Int) + childCapacity: number; + + @Field() + isLuxury: boolean; +} + +@ObjectType() +export class GlampsPageInfo { + @Field() + hasNextPage: boolean; + + @Field({ nullable: true }) + total: number; +} + +@ObjectType() +export class GlampsResponse { + @Field(() => [Glamp]) + glamps: Glamp[]; + + @Field() + pageInfo: GlampsPageInfo; +} diff --git a/src/pages/api/graphql.ts b/src/pages/api/graphql.ts index 4f72f18..9f4e65a 100644 --- a/src/pages/api/graphql.ts +++ b/src/pages/api/graphql.ts @@ -6,9 +6,15 @@ import { UserResolver } from "@/graphql/schema/users/users.resolver"; import type { NextApiRequest, NextApiResponse } from "next"; import { DealResolver } from "@/graphql/schema/deals/deals.resolver"; import { ProductResolver } from "@/graphql/schema/product/product.resolver"; +import { GlampResolver } from "@/graphql/schema/glamps/glamps.resolver"; + +export interface MyContext { + req: NextApiRequest; + res: NextApiResponse; +} const schema = await buildSchema({ - resolvers: [ProductResolver, UserResolver, DealResolver], + resolvers: [ProductResolver, UserResolver, DealResolver, GlampResolver], nullableByDefault: true, }); @@ -17,6 +23,7 @@ const server = new ApolloServer({ csrfPrevention: false, cache: "bounded", plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })], + context: ({ req, res }): MyContext => ({ req, res }), }); export const config = { diff --git a/src/pages/offset-pagination-expanded/index.tsx b/src/pages/offset-pagination-expanded/index.tsx index ae97aed..6de8317 100644 --- a/src/pages/offset-pagination-expanded/index.tsx +++ b/src/pages/offset-pagination-expanded/index.tsx @@ -102,7 +102,7 @@ export default function BasedPagitanionPage() { } export async function getServerSideProps() { - const apolloClient = initializeApollo(); + const apolloClient = initializeApollo({}); await apolloClient.query({ query: GetDealsOffsetBasedExpandedDocument, diff --git a/src/pages/offset-pagination/index.tsx b/src/pages/offset-pagination/index.tsx index ac197fa..ac3424a 100644 --- a/src/pages/offset-pagination/index.tsx +++ b/src/pages/offset-pagination/index.tsx @@ -62,7 +62,7 @@ export default function BasedPagitanionPage() { } export async function getServerSideProps() { - const apolloClient = initializeApollo(); + const apolloClient = initializeApollo({}); await apolloClient.query({ query: GetDealsOffsetBasedDocument, diff --git a/src/pages/refetch-changing/index.tsx b/src/pages/refetch-changing/index.tsx new file mode 100644 index 0000000..a73e8cb --- /dev/null +++ b/src/pages/refetch-changing/index.tsx @@ -0,0 +1,242 @@ +import ExampleExplanation from "@/components/ExampleExplanation"; +import { Button } from "@/components/ui/button"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Loader } from "lucide-react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { Checkbox } from "@/components/ui/checkbox"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { useCreateDealMutation, useGetDealsQuery } from "@/generated/graphql"; +import { cn } from "@/lib/utils"; + +const EXPLANATION = { + title: "Refetching - observations", + description: "Zkoumání", +}; + +export default function RefetchPage() { + const { data, loading, refetch } = useGetDealsQuery(); + if (loading) { + return
Loading...
; + } + + return ( +
+ +
+

Poznámky

+
    +
  • + Když nastavím fetch policy standby, tak se ta komponenta nezmění + pokud je refetch z jiné komponenty. +
  • +
+
+
+ +
+ {data?.deals.length && ( +
+ + {data.deals && + data.deals.map((deal) => ( +
+

+ {deal.title} + + ({deal.isActive ? "true" : "false"}) + +

+
+ ))} +
+ )} + +
+
+ ); +} + +const DealFormSchema = z.object({ + title: z.string().min(2, { + message: "Deal title must be at least 2 characters.", + }), + description: z.string().min(2, { + message: "Deal description must be at least 2 characters.", + }), + isActive: z.boolean().default(false).optional(), +}); + +type DealInputs = z.infer; + +const CreateDealForm = () => { + const form = useForm({ + resolver: zodResolver(DealFormSchema), + defaultValues: { + title: "", + description: "", + }, + }); + + const [mutation, { loading }] = useCreateDealMutation(); + + function onSubmit(data: DealInputs) { + mutation({ + variables: { + title: data.title, + description: data.description, + isActive: data.isActive, + }, + }); + form.reset(); + } + + return ( +
+ + ( + + Title + + + + + + )} + /> + ( + + Description + + + + + + )} + /> + ( + + + + +
+ isActive +
+
+ )} + /> + + + + ); +}; + +const ShowDeals = () => { + const { data, loading, error, refetch } = useGetDealsQuery(); + + if (data?.deals.length) + return ( +
+
+ + + {data.deals && + data.deals.map((deal) => ( +
+

+ {deal.title} + + ({deal.isActive ? "true" : "false"}) + +

+
+ ))} +
+ +
+ ); + + if (loading) { + return
Loading...
; + } + + if (error) { + return
Error..
; + } +}; + +export const ShowDealsGrandChildren = () => { + const { data, loading, error, refetch } = useGetDealsQuery({ + fetchPolicy: "standby", + }); + + console.log(data); + if (data?.deals.length) + return ( +
+ + {data.deals && + data.deals.map((deal) => ( +
+

+ {deal.title} + + ({deal.isActive ? "true" : "false"}) + +

+
+ ))} +
+ ); + + if (loading) { + return
Loading...
; + } + + if (error) { + return
Error..
; + } +};