Skip to content

Commit

Permalink
Allow to remove deck (#13)
Browse files Browse the repository at this point in the history
* Allow to remove deck
  • Loading branch information
kubk authored Nov 21, 2023
1 parent 9b8d5d3 commit e26cff0
Show file tree
Hide file tree
Showing 23 changed files with 345 additions and 102 deletions.
8 changes: 1 addition & 7 deletions functions/db/databaseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,7 @@ export interface Database {
Returns: {
id: number
deck_id: number
}[]
}
get_review_counts_per_user: {
Args: Record<PropertyKey, never>
Returns: {
user_id: number
review_count: number
type: string
}[]
}
get_user_decks_deck_id: {
Expand Down
1 change: 1 addition & 0 deletions functions/db/deck/get-cards-to-review-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { z } from "zod";
const cardToReviewSchema = z.object({
id: z.number(),
deck_id: z.number(),
type: z.enum(["new", "repeat"]),
});

const schema = z.array(cardToReviewSchema);
Expand Down
18 changes: 18 additions & 0 deletions functions/db/deck/remove-deck-from-mine-db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { EnvType } from "../../env/env-schema.ts";
import { getDatabase } from "../get-database.ts";
import { DatabaseException } from "../database-exception.ts";

export const removeDeckFromMineDb = async (
env: EnvType,
body: { user_id: number; deck_id: number },
): Promise<null> => {
const db = getDatabase(env);

const { error } = await db.from("user_deck").delete().match(body);

if (error) {
throw new DatabaseException(error);
}

return null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const userDbSchema = z.object({

export type UserDbType = z.infer<typeof userDbSchema>;

export const createOrUpdateUserDb = async (
export const upsertUserDb = async (
env: EnvType,
user: UserTelegramType,
): Promise<UserDbType> => {
Expand Down
2 changes: 1 addition & 1 deletion functions/my-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createJsonResponse } from "./lib/json-response/create-json-response.ts"
import { getUser } from "./services/get-user.ts";
import { createAuthFailedResponse } from "./lib/json-response/create-auth-failed-response.ts";
import { handleError } from "./lib/handle-error/handle-error.ts";
import { UserDbType } from "./db/user/create-or-update-user-db.ts";
import { UserDbType } from "./db/user/upsert-user-db.ts";
import { DeckWithCardsDbType } from "./db/deck/decks-with-cards-schema.ts";
import { getPublicDecksDb } from "./db/deck/get-public-decks-db.ts";
import { envSchema } from "./env/env-schema.ts";
Expand Down
44 changes: 44 additions & 0 deletions functions/remove-deck-from-mine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { handleError } from "./lib/handle-error/handle-error.ts";
import { getUser } from "./services/get-user.ts";
import { createAuthFailedResponse } from "./lib/json-response/create-auth-failed-response.ts";
import { z } from "zod";
import { envSchema } from "./env/env-schema.ts";
import { createBadRequestResponse } from "./lib/json-response/create-bad-request-response.ts";
import { createJsonResponse } from "./lib/json-response/create-json-response.ts";
import { removeDeckFromMineDb } from "./db/deck/remove-deck-from-mine-db.ts";
import { getDeckByIdAndAuthorId } from "./db/deck/get-deck-by-id-and-author-id.ts";
import { createForbiddenRequestResponse } from "./lib/json-response/create-forbidden-request-response.ts";

const requestSchema = z.object({
deckId: z.number(),
});

export type RemoveDeckFromMineRequest = z.infer<typeof requestSchema>;
export type RemoveDeckFromMineResponse = null;

export const onRequestPost = handleError(async ({ env, request }) => {
const user = await getUser(request, env);
if (!user) return createAuthFailedResponse();
const input = requestSchema.safeParse(await request.json());
if (!input.success) {
return createBadRequestResponse();
}

const envSafe = envSchema.parse(env);

const canEdit = await getDeckByIdAndAuthorId(
envSafe,
input.data.deckId,
user.id,
);
if (!canEdit) {
return createForbiddenRequestResponse();
}

await removeDeckFromMineDb(envSafe, {
user_id: user.id,
deck_id: input.data.deckId,
});

return createJsonResponse<RemoveDeckFromMineResponse>(null);
});
7 changes: 2 additions & 5 deletions functions/services/get-user.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { envSchema } from "../env/env-schema.ts";
import { validateTelegramRequest } from "../lib/telegram/validate-telegram-request.ts";
import {
createOrUpdateUserDb,
UserDbType,
} from "../db/user/create-or-update-user-db.ts";
import { upsertUserDb, UserDbType } from "../db/user/upsert-user-db.ts";

export const getUser = async (
request: Request,
Expand All @@ -21,5 +18,5 @@ export const getUser = async (
return null;
}

return createOrUpdateUserDb(envSafe, result);
return upsertUserDb(envSafe, result);
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"test:frontend:coverage": "npx vitest run --dir src/ --coverage",
"prod:api:logs": "npx wrangler pages deployment tail",
"prod:deploy": "npx wrangler pages publish functions --project-name=memo-card",
"generate-db-types": "export $(cat .dev.vars | xargs) && npx supabase gen types typescript --db-url postgresql://postgres:$DB_PASS@$DB_HOST:5432/$DB_NAME >./functions/db/databaseTypes.ts"
"db:backup": "export $(cat .dev.vars | xargs) && pg_dump postgresql://postgres:$DB_PASS@$DB_HOST:5432/$DB_NAME > dump.sql",
"generate-db-types": "docker info > /dev/null 2>&1 && export $(cat .dev.vars | xargs) && npx supabase gen types typescript --db-url postgresql://postgres:$DB_PASS@$DB_HOST:5432/$DB_NAME >./functions/db/databaseTypes.ts"
},
"pre-commit": [
"lint",
Expand Down
12 changes: 12 additions & 0 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import {
UserSettingsRequest,
UserSettingsResponse,
} from "../../functions/user-settings.ts";
import {
RemoveDeckFromMineRequest,
RemoveDeckFromMineResponse,
} from "../../functions/remove-deck-from-mine.ts";

export const healthRequest = () => {
return request<HealthResponse>("/health");
Expand Down Expand Up @@ -67,3 +71,11 @@ export const upsertDeckRequest = (body: UpsertDeckRequest) => {
export const addCardRequest = (body: AddCardRequest) => {
return request<AddCardResponse, AddCardRequest>("/add-card", "POST", body);
};

export const removeDeckFromMine = (body: RemoveDeckFromMineRequest) => {
return request<RemoveDeckFromMineResponse, RemoveDeckFromMineRequest>(
"/remove-deck-from-mine",
"POST",
body,
);
};
6 changes: 6 additions & 0 deletions src/screens/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ import { VersionWarning } from "./shared/version-warning.tsx";
import React from "react";
import { UserSettingsStoreProvider } from "../store/user-settings-store-context.tsx";
import { UserSettingsMain } from "./user-settings/user-settings-main.tsx";
import { deckListStore } from "../store/deck-list-store.ts";
import { FullScreenLoader } from "./deck-list/full-screen-loader.tsx";

export const App = observer(() => {
if (deckListStore.isSharedDeckLoading || deckListStore.isDeckRemoving) {
return <FullScreenLoader />;
}

return (
<div>
<VersionWarning />
Expand Down
23 changes: 23 additions & 0 deletions src/screens/deck-list/cards-to-review-count.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { css } from "@emotion/css";
import React from "react";
import { observer } from "mobx-react-lite";

type Props = {
items: Array<unknown>;
color: string;
};

export const CardsToReviewCount = observer((props: Props) => {
const { items, color } = props;

return items.length > 0 ? (
<div
className={css({
color: color,
fontWeight: 600,
})}
>
{items.length}
</div>
) : null;
});
19 changes: 19 additions & 0 deletions src/screens/deck-list/full-screen-loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { css } from "@emotion/css";
import { theme } from "../../ui/theme.tsx";
import React from "react";

export const FullScreenLoader = () => {
return (
<div
className={css({
display: "flex",
height: "100vh",
backgroundColor: theme.bgColor,
alignItems: "center",
justifyContent: "center",
})}
>
<i className={"mdi mdi-loading mdi-spin mdi-48px"} />
</div>
);
};
19 changes: 1 addition & 18 deletions src/screens/deck-list/main-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,9 @@ import { range } from "../../lib/array/range.ts";

export const MainScreen = observer(() => {
useMount(() => {
deckListStore.load();
deckListStore.loadSharedDeck(WebApp.initDataUnsafe.start_param);
deckListStore.loadFirstTime(WebApp.initDataUnsafe.start_param);
});

if (deckListStore.isSharedDeckLoading) {
return (
<div
className={css({
display: "flex",
height: "100vh",
backgroundColor: theme.bgColor,
alignItems: "center",
justifyContent: "center",
})}
>
<i className={"mdi mdi-loading mdi-spin mdi-48px"} />
</div>
);
}

return (
<div
className={css({
Expand Down
19 changes: 15 additions & 4 deletions src/screens/deck-list/my-deck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import { motion } from "framer-motion";
import { whileTap } from "../../ui/animations.ts";
import { screenStore } from "../../store/screen-store.ts";
import { DeckWithCardsWithReviewType } from "../../store/deck-list-store.ts";
import { CardsToReviewCount } from "./cards-to-review-count.tsx";

type Props = { deck: DeckWithCardsWithReviewType };
type Props = {
deck: DeckWithCardsWithReviewType;
};

export const MyDeck = observer((props: Props) => {
const { deck } = props;
Expand Down Expand Up @@ -40,11 +43,19 @@ export const MyDeck = observer((props: Props) => {
</div>
<div
className={css({
color: theme.success,
fontWeight: 600,
display: "flex",
justifyContent: "space-between",
gap: 10,
})}
>
{deck.cardsToReview.length}
<CardsToReviewCount
items={deck.cardsToReview.filter((card) => card.type === "repeat")}
color={theme.orange}
/>
<CardsToReviewCount
items={deck.cardsToReview.filter((card) => card.type === "new")}
color={theme.success}
/>
</div>
</motion.div>
);
Expand Down
4 changes: 3 additions & 1 deletion src/screens/deck-list/public-deck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { motion } from "framer-motion";
import { DeckWithCardsDbType } from "../../../functions/db/deck/decks-with-cards-schema.ts";
import { screenStore } from "../../store/screen-store.ts";

type Props = { deck: DeckWithCardsDbType };
type Props = {
deck: DeckWithCardsDbType;
};

export const PublicDeck = observer((props: Props) => {
const { deck } = props;
Expand Down
5 changes: 1 addition & 4 deletions src/screens/deck-review/deck-finished.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@ const encouragingMessages = [
"It's all part of ensuring these nuggets of knowledge stick with you for the long run.",
"Each review session carves the knowledge deeper into your memory. Well done for pushing through!",
"Every review strengthens your neural connections. You're not just learning; you're growing!",
"Remember, every great mind, from artists to scientists, spent time reviewing and honing their craft. You're on the right track!",
"You're fueling your future self with every review. Imagine where you'll be a year from now!",
"As the saying goes, 'Repetition is the mother of learning.' You're embracing this wisdom with every session.",
"Your commitment today is the foundation for mastery tomorrow. Keep it up!",
"Learning is like building a castle brick by brick. Every review adds another stone to your fortress of knowledge.",
"Every moment spent in review today saves hours of re-learning in the future. You're on the fast track!",
"Remember, the mightiest of trees grow from constant nurturing. Your knowledge is no different. Keep watering your learning tree!",
"You're not just revisiting information; you're turning it into a part of who you are. Well done!",
"With every review, you're stepping further away from forgetting and closer to internalizing. Great job!",
"Just think of the compounded knowledge you're amassing with every review. Your future self thanks you!",
"Just think of the compounded knowledge you're getting with every review. Your future self thanks you!",
];

export const DeckFinished = observer(() => {
Expand Down
Loading

0 comments on commit e26cff0

Please sign in to comment.