From fe681c3af9903b9c22b1295825444e1728089cbe Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 15 Oct 2024 22:40:47 -0300 Subject: [PATCH] feat: create UserSubscription --- src/main/data-source.ts | 4 +- src/main/entity/index.ts | 3 +- .../{user-auth.ts => user-auth.entity.ts} | 5 +++ src/main/entity/user-subscription.entity.ts | 42 +++++++++++++++++++ src/main/events/profile/get-me.ts | 39 +++++++++++++++-- src/main/knex-client.ts | 2 + ...20241015235142_create_user_subscription.ts | 27 ++++++++++++ src/main/migrations/migration.stub | 4 +- src/main/repository.ts | 4 ++ .../achievements/achievement-watcher.ts | 4 +- .../achievements/get-game-achievement-data.ts | 3 +- .../achievements/merge-achievements.ts | 14 ++++--- src/renderer/src/hooks/use-user-details.ts | 1 + src/types/index.ts | 10 +++++ yarn.lock | 2 +- 15 files changed, 147 insertions(+), 17 deletions(-) rename src/main/entity/{user-auth.ts => user-auth.entity.ts} (80%) create mode 100644 src/main/entity/user-subscription.entity.ts create mode 100644 src/main/migrations/20241015235142_create_user_subscription.ts diff --git a/src/main/data-source.ts b/src/main/data-source.ts index 9745abd8..80a40f47 100644 --- a/src/main/data-source.ts +++ b/src/main/data-source.ts @@ -8,6 +8,7 @@ import { UserPreferences, UserAuth, GameAchievement, + UserSubscription, } from "@main/entity"; import { databasePath } from "./constants"; @@ -17,11 +18,12 @@ export const dataSource = new DataSource({ entities: [ Game, Repack, + UserAuth, UserPreferences, + UserSubscription, GameShopCache, DownloadSource, DownloadQueue, - UserAuth, GameAchievement, ], synchronize: false, diff --git a/src/main/entity/index.ts b/src/main/entity/index.ts index 7e52577c..5829e6a2 100644 --- a/src/main/entity/index.ts +++ b/src/main/entity/index.ts @@ -1,9 +1,10 @@ export * from "./game.entity"; export * from "./repack.entity"; +export * from "./user-auth.entity"; export * from "./user-preferences.entity"; +export * from "./user-subscription.entity"; export * from "./game-shop-cache.entity"; export * from "./game.entity"; export * from "./game-achievements.entity"; export * from "./download-source.entity"; export * from "./download-queue.entity"; -export * from "./user-auth"; diff --git a/src/main/entity/user-auth.ts b/src/main/entity/user-auth.entity.ts similarity index 80% rename from src/main/entity/user-auth.ts rename to src/main/entity/user-auth.entity.ts index 6bfd6ad7..f63b19d0 100644 --- a/src/main/entity/user-auth.ts +++ b/src/main/entity/user-auth.entity.ts @@ -4,7 +4,9 @@ import { Column, CreateDateColumn, UpdateDateColumn, + OneToOne, } from "typeorm"; +import { UserSubscription } from "./user-subscription.entity"; @Entity("user_auth") export class UserAuth { @@ -29,6 +31,9 @@ export class UserAuth { @Column("int", { default: 0 }) tokenExpirationTimestamp: number; + @OneToOne("UserSubscription", "user") + subscription: UserSubscription | null; + @CreateDateColumn() createdAt: Date; diff --git a/src/main/entity/user-subscription.entity.ts b/src/main/entity/user-subscription.entity.ts new file mode 100644 index 00000000..e74ada48 --- /dev/null +++ b/src/main/entity/user-subscription.entity.ts @@ -0,0 +1,42 @@ +import type { SubscriptionStatus } from "@types"; +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + OneToOne, + JoinColumn, +} from "typeorm"; +import { UserAuth } from "./user-auth.entity"; + +@Entity("user_subscription") +export class UserSubscription { + @PrimaryGeneratedColumn() + id: number; + + @Column("text", { default: "" }) + subscriptionId: string; + + @OneToOne("UserAuth", "subscription") + @JoinColumn() + user: UserAuth; + + @Column("text", { default: "" }) + status: SubscriptionStatus; + + @Column("text", { default: "" }) + planId: string; + + @Column("text", { default: "" }) + planName: string; + + @Column("datetime", { nullable: true }) + expiresAt: Date | null; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +} diff --git a/src/main/events/profile/get-me.ts b/src/main/events/profile/get-me.ts index 1eeecbf9..81c74f22 100644 --- a/src/main/events/profile/get-me.ts +++ b/src/main/events/profile/get-me.ts @@ -1,15 +1,18 @@ import { registerEvent } from "../register-event"; import * as Sentry from "@sentry/electron/main"; -import { HydraApi } from "@main/services"; +import { HydraApi, logger } from "@main/services"; import { ProfileVisibility, UserDetails } from "@types"; -import { userAuthRepository } from "@main/repository"; +import { + userAuthRepository, + userSubscriptionRepository, +} from "@main/repository"; import { UserNotLoggedInError } from "@shared"; const getMe = async ( _event: Electron.IpcMainInvokeEvent ): Promise => { return HydraApi.get(`/profile/me`) - .then(async (me) => { + .then((me) => { userAuthRepository.upsert( { id: 1, @@ -20,6 +23,23 @@ const getMe = async ( ["id"] ); + if (me.subscription) { + userSubscriptionRepository.upsert( + { + id: 1, + subscriptionId: me.subscription?.id || "", + status: me.subscription?.status || "", + planId: me.subscription?.plan.id || "", + planName: me.subscription?.plan.name || "", + expiresAt: me.subscription?.expiresAt || null, + user: { id: 1 }, + }, + ["id"] + ); + } else { + userSubscriptionRepository.delete({ id: 1 }); + } + Sentry.setUser({ id: me.id, username: me.username }); return me; @@ -28,7 +48,7 @@ const getMe = async ( if (err instanceof UserNotLoggedInError) { return null; } - + logger.error("Failed to get logged user", err); const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } }); if (loggedUser) { @@ -38,6 +58,17 @@ const getMe = async ( username: "", bio: "", profileVisibility: "PUBLIC" as ProfileVisibility, + subscription: loggedUser.subscription + ? { + id: loggedUser.subscription.subscriptionId, + status: loggedUser.subscription.status, + plan: { + id: loggedUser.subscription.planId, + name: loggedUser.subscription.planName, + }, + expiresAt: loggedUser.subscription.expiresAt, + } + : null, }; } diff --git a/src/main/knex-client.ts b/src/main/knex-client.ts index c289eebe..c9be5437 100644 --- a/src/main/knex-client.ts +++ b/src/main/knex-client.ts @@ -8,6 +8,7 @@ import { app } from "electron"; import { FixMissingColumns } from "./migrations/20240918001920_FixMissingColumns"; import { CreateGameAchievement } from "./migrations/20240919030940_create_game_achievement"; import { AddAchievementNotificationPreference } from "./migrations/20241013012900_add_achievement_notification_preference"; +import { CreateUserSubscription } from "./migrations/20241015235142_create_user_subscription"; export type HydraMigration = Knex.Migration & { name: string }; @@ -21,6 +22,7 @@ class MigrationSource implements Knex.MigrationSource { FixMissingColumns, CreateGameAchievement, AddAchievementNotificationPreference, + CreateUserSubscription, ]); } getMigrationName(migration: HydraMigration): string { diff --git a/src/main/migrations/20241015235142_create_user_subscription.ts b/src/main/migrations/20241015235142_create_user_subscription.ts new file mode 100644 index 00000000..5f9ecab1 --- /dev/null +++ b/src/main/migrations/20241015235142_create_user_subscription.ts @@ -0,0 +1,27 @@ +import type { HydraMigration } from "@main/knex-client"; +import type { Knex } from "knex"; + +export const CreateUserSubscription: HydraMigration = { + name: "CreateUserSubscription", + up: async (knex: Knex) => { + return knex.schema.createTable("user_subscription", (table) => { + table.increments("id").primary(); + table.string("subscriptionId").defaultTo(""); + table + .text("userId") + .notNullable() + .references("user_auth.id") + .onDelete("CASCADE"); + table.string("status").defaultTo(""); + table.string("planId").defaultTo(""); + table.string("planName").defaultTo(""); + table.dateTime("expiresAt").nullable(); + table.dateTime("createdAt").defaultTo(knex.fn.now()); + table.dateTime("updatedAt").defaultTo(knex.fn.now()); + }); + }, + + down: async (knex: Knex) => { + return knex.schema.dropTable("user_subscription"); + }, +}; diff --git a/src/main/migrations/migration.stub b/src/main/migrations/migration.stub index 9cb0cbab..299b3fc2 100644 --- a/src/main/migrations/migration.stub +++ b/src/main/migrations/migration.stub @@ -3,8 +3,8 @@ import type { Knex } from "knex"; export const MigrationName: HydraMigration = { name: "MigrationName", - up: async (knex: Knex) => { - await knex.schema.createTable("table_name", (table) => {}); + up: (knex: Knex) => { + return knex.schema.createTable("table_name", async (table) => {}); }, down: async (knex: Knex) => {}, diff --git a/src/main/repository.ts b/src/main/repository.ts index 4e5c115f..cf3ab143 100644 --- a/src/main/repository.ts +++ b/src/main/repository.ts @@ -8,6 +8,7 @@ import { UserPreferences, UserAuth, GameAchievement, + UserSubscription, } from "@main/entity"; export const gameRepository = dataSource.getRepository(Game); @@ -26,5 +27,8 @@ export const downloadQueueRepository = dataSource.getRepository(DownloadQueue); export const userAuthRepository = dataSource.getRepository(UserAuth); +export const userSubscriptionRepository = + dataSource.getRepository(UserSubscription); + export const gameAchievementRepository = dataSource.getRepository(GameAchievement); diff --git a/src/main/services/achievements/achievement-watcher.ts b/src/main/services/achievements/achievement-watcher.ts index 166e6cff..ac078468 100644 --- a/src/main/services/achievements/achievement-watcher.ts +++ b/src/main/services/achievements/achievement-watcher.ts @@ -113,8 +113,8 @@ const compareFile = async (game: Game, file: AchievementFile) => { logger.log( "Detected change in file", file.filePath, - currentStat.mtimeMs, - fileStats.get(file.filePath) + previousStat, + currentStat.mtimeMs ); await processAchievementFileDiff(game, file); } catch (err) { diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index b94cbe7f..443af59a 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -5,6 +5,7 @@ import { import { HydraApi } from "../hydra-api"; import { AchievementData } from "@types"; import { UserNotLoggedInError } from "@shared"; +import { logger } from "../logger"; export const getGameAchievementData = async ( objectId: string, @@ -35,7 +36,7 @@ export const getGameAchievementData = async ( if (err instanceof UserNotLoggedInError) { throw err; } - + logger.error("Failed to get game achievements", err); return gameAchievementRepository .findOne({ where: { objectId, shop }, diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index b7c50d37..ae2ba3ed 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -22,11 +22,15 @@ const saveAchievementsOnLocal = async ( }, ["objectId", "shop"] ) - .then(async () => { - WindowManager.mainWindow?.webContents.send( - `on-update-achievements-${objectId}-${shop}`, - await getGameAchievements(objectId, shop as GameShop) - ); + .then(() => { + return getGameAchievements(objectId, shop as GameShop) + .then((achievements) => { + WindowManager.mainWindow?.webContents.send( + `on-update-achievements-${objectId}-${shop}`, + achievements + ); + }) + .catch(() => {}); }); }; diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 7e08144d..50689aeb 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -67,6 +67,7 @@ export function useUserDetails() { return updateUserDetails({ ...response, username: userDetails?.username || "", + subscription: userDetails?.subscription || null, }); }, [updateUserDetails, userDetails?.username] diff --git a/src/types/index.ts b/src/types/index.ts index 987e9f32..5408cbc3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -230,6 +230,15 @@ export interface UserProfileCurrentGame extends Omit { export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS"; +export type SubscriptionStatus = "active" | "pending" | "cancelled"; + +export interface Subscription { + id: string; + status: SubscriptionStatus; + plan: { id: string; name: string }; + expiresAt: Date | null; +} + export interface UserDetails { id: string; username: string; @@ -237,6 +246,7 @@ export interface UserDetails { profileImageUrl: string | null; profileVisibility: ProfileVisibility; bio: string; + subscription: Subscription | null; } export interface UserProfile { diff --git a/yarn.lock b/yarn.lock index d98e97dd..51391923 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5347,7 +5347,7 @@ i18next@^23.11.2: dependencies: "@babel/runtime" "^7.23.2" -icojs@^0.19.3: +icojs@^0.19.4: version "0.19.4" resolved "https://registry.yarnpkg.com/icojs/-/icojs-0.19.4.tgz#fdbc9e61a0945ed1d331beb358d67f72cf7d78dc" integrity sha512-86oNepPk2jAmbb96BPeucZI7HoSBobFlXDhhjIbwRb3wkQpvdBO5HO9KtMUNzMFT3qqQZsjLsfW+L0/9Rl9VqA==