feat: create UserSubscription

This commit is contained in:
Zamitto 2024-10-15 22:40:47 -03:00
parent 8ff925fbb9
commit fe681c3af9
15 changed files with 147 additions and 17 deletions

View File

@ -8,6 +8,7 @@ import {
UserPreferences, UserPreferences,
UserAuth, UserAuth,
GameAchievement, GameAchievement,
UserSubscription,
} from "@main/entity"; } from "@main/entity";
import { databasePath } from "./constants"; import { databasePath } from "./constants";
@ -17,11 +18,12 @@ export const dataSource = new DataSource({
entities: [ entities: [
Game, Game,
Repack, Repack,
UserAuth,
UserPreferences, UserPreferences,
UserSubscription,
GameShopCache, GameShopCache,
DownloadSource, DownloadSource,
DownloadQueue, DownloadQueue,
UserAuth,
GameAchievement, GameAchievement,
], ],
synchronize: false, synchronize: false,

View File

@ -1,9 +1,10 @@
export * from "./game.entity"; export * from "./game.entity";
export * from "./repack.entity"; export * from "./repack.entity";
export * from "./user-auth.entity";
export * from "./user-preferences.entity"; export * from "./user-preferences.entity";
export * from "./user-subscription.entity";
export * from "./game-shop-cache.entity"; export * from "./game-shop-cache.entity";
export * from "./game.entity"; export * from "./game.entity";
export * from "./game-achievements.entity"; export * from "./game-achievements.entity";
export * from "./download-source.entity"; export * from "./download-source.entity";
export * from "./download-queue.entity"; export * from "./download-queue.entity";
export * from "./user-auth";

View File

@ -4,7 +4,9 @@ import {
Column, Column,
CreateDateColumn, CreateDateColumn,
UpdateDateColumn, UpdateDateColumn,
OneToOne,
} from "typeorm"; } from "typeorm";
import { UserSubscription } from "./user-subscription.entity";
@Entity("user_auth") @Entity("user_auth")
export class UserAuth { export class UserAuth {
@ -29,6 +31,9 @@ export class UserAuth {
@Column("int", { default: 0 }) @Column("int", { default: 0 })
tokenExpirationTimestamp: number; tokenExpirationTimestamp: number;
@OneToOne("UserSubscription", "user")
subscription: UserSubscription | null;
@CreateDateColumn() @CreateDateColumn()
createdAt: Date; createdAt: Date;

View File

@ -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;
}

View File

@ -1,15 +1,18 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import * as Sentry from "@sentry/electron/main"; import * as Sentry from "@sentry/electron/main";
import { HydraApi } from "@main/services"; import { HydraApi, logger } from "@main/services";
import { ProfileVisibility, UserDetails } from "@types"; import { ProfileVisibility, UserDetails } from "@types";
import { userAuthRepository } from "@main/repository"; import {
userAuthRepository,
userSubscriptionRepository,
} from "@main/repository";
import { UserNotLoggedInError } from "@shared"; import { UserNotLoggedInError } from "@shared";
const getMe = async ( const getMe = async (
_event: Electron.IpcMainInvokeEvent _event: Electron.IpcMainInvokeEvent
): Promise<UserDetails | null> => { ): Promise<UserDetails | null> => {
return HydraApi.get<UserDetails>(`/profile/me`) return HydraApi.get<UserDetails>(`/profile/me`)
.then(async (me) => { .then((me) => {
userAuthRepository.upsert( userAuthRepository.upsert(
{ {
id: 1, id: 1,
@ -20,6 +23,23 @@ const getMe = async (
["id"] ["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 }); Sentry.setUser({ id: me.id, username: me.username });
return me; return me;
@ -28,7 +48,7 @@ const getMe = async (
if (err instanceof UserNotLoggedInError) { if (err instanceof UserNotLoggedInError) {
return null; return null;
} }
logger.error("Failed to get logged user", err);
const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } }); const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } });
if (loggedUser) { if (loggedUser) {
@ -38,6 +58,17 @@ const getMe = async (
username: "", username: "",
bio: "", bio: "",
profileVisibility: "PUBLIC" as ProfileVisibility, 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,
}; };
} }

View File

@ -8,6 +8,7 @@ import { app } from "electron";
import { FixMissingColumns } from "./migrations/20240918001920_FixMissingColumns"; import { FixMissingColumns } from "./migrations/20240918001920_FixMissingColumns";
import { CreateGameAchievement } from "./migrations/20240919030940_create_game_achievement"; import { CreateGameAchievement } from "./migrations/20240919030940_create_game_achievement";
import { AddAchievementNotificationPreference } from "./migrations/20241013012900_add_achievement_notification_preference"; import { AddAchievementNotificationPreference } from "./migrations/20241013012900_add_achievement_notification_preference";
import { CreateUserSubscription } from "./migrations/20241015235142_create_user_subscription";
export type HydraMigration = Knex.Migration & { name: string }; export type HydraMigration = Knex.Migration & { name: string };
@ -21,6 +22,7 @@ class MigrationSource implements Knex.MigrationSource<HydraMigration> {
FixMissingColumns, FixMissingColumns,
CreateGameAchievement, CreateGameAchievement,
AddAchievementNotificationPreference, AddAchievementNotificationPreference,
CreateUserSubscription,
]); ]);
} }
getMigrationName(migration: HydraMigration): string { getMigrationName(migration: HydraMigration): string {

View File

@ -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");
},
};

View File

@ -3,8 +3,8 @@ import type { Knex } from "knex";
export const MigrationName: HydraMigration = { export const MigrationName: HydraMigration = {
name: "MigrationName", name: "MigrationName",
up: async (knex: Knex) => { up: (knex: Knex) => {
await knex.schema.createTable("table_name", (table) => {}); return knex.schema.createTable("table_name", async (table) => {});
}, },
down: async (knex: Knex) => {}, down: async (knex: Knex) => {},

View File

@ -8,6 +8,7 @@ import {
UserPreferences, UserPreferences,
UserAuth, UserAuth,
GameAchievement, GameAchievement,
UserSubscription,
} from "@main/entity"; } from "@main/entity";
export const gameRepository = dataSource.getRepository(Game); export const gameRepository = dataSource.getRepository(Game);
@ -26,5 +27,8 @@ export const downloadQueueRepository = dataSource.getRepository(DownloadQueue);
export const userAuthRepository = dataSource.getRepository(UserAuth); export const userAuthRepository = dataSource.getRepository(UserAuth);
export const userSubscriptionRepository =
dataSource.getRepository(UserSubscription);
export const gameAchievementRepository = export const gameAchievementRepository =
dataSource.getRepository(GameAchievement); dataSource.getRepository(GameAchievement);

View File

@ -113,8 +113,8 @@ const compareFile = async (game: Game, file: AchievementFile) => {
logger.log( logger.log(
"Detected change in file", "Detected change in file",
file.filePath, file.filePath,
currentStat.mtimeMs, previousStat,
fileStats.get(file.filePath) currentStat.mtimeMs
); );
await processAchievementFileDiff(game, file); await processAchievementFileDiff(game, file);
} catch (err) { } catch (err) {

View File

@ -5,6 +5,7 @@ import {
import { HydraApi } from "../hydra-api"; import { HydraApi } from "../hydra-api";
import { AchievementData } from "@types"; import { AchievementData } from "@types";
import { UserNotLoggedInError } from "@shared"; import { UserNotLoggedInError } from "@shared";
import { logger } from "../logger";
export const getGameAchievementData = async ( export const getGameAchievementData = async (
objectId: string, objectId: string,
@ -35,7 +36,7 @@ export const getGameAchievementData = async (
if (err instanceof UserNotLoggedInError) { if (err instanceof UserNotLoggedInError) {
throw err; throw err;
} }
logger.error("Failed to get game achievements", err);
return gameAchievementRepository return gameAchievementRepository
.findOne({ .findOne({
where: { objectId, shop }, where: { objectId, shop },

View File

@ -22,11 +22,15 @@ const saveAchievementsOnLocal = async (
}, },
["objectId", "shop"] ["objectId", "shop"]
) )
.then(async () => { .then(() => {
WindowManager.mainWindow?.webContents.send( return getGameAchievements(objectId, shop as GameShop)
`on-update-achievements-${objectId}-${shop}`, .then((achievements) => {
await getGameAchievements(objectId, shop as GameShop) WindowManager.mainWindow?.webContents.send(
); `on-update-achievements-${objectId}-${shop}`,
achievements
);
})
.catch(() => {});
}); });
}; };

View File

@ -67,6 +67,7 @@ export function useUserDetails() {
return updateUserDetails({ return updateUserDetails({
...response, ...response,
username: userDetails?.username || "", username: userDetails?.username || "",
subscription: userDetails?.subscription || null,
}); });
}, },
[updateUserDetails, userDetails?.username] [updateUserDetails, userDetails?.username]

View File

@ -230,6 +230,15 @@ export interface UserProfileCurrentGame extends Omit<GameRunning, "objectId"> {
export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS"; 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 { export interface UserDetails {
id: string; id: string;
username: string; username: string;
@ -237,6 +246,7 @@ export interface UserDetails {
profileImageUrl: string | null; profileImageUrl: string | null;
profileVisibility: ProfileVisibility; profileVisibility: ProfileVisibility;
bio: string; bio: string;
subscription: Subscription | null;
} }
export interface UserProfile { export interface UserProfile {

View File

@ -5347,7 +5347,7 @@ i18next@^23.11.2:
dependencies: dependencies:
"@babel/runtime" "^7.23.2" "@babel/runtime" "^7.23.2"
icojs@^0.19.3: icojs@^0.19.4:
version "0.19.4" version "0.19.4"
resolved "https://registry.yarnpkg.com/icojs/-/icojs-0.19.4.tgz#fdbc9e61a0945ed1d331beb358d67f72cf7d78dc" resolved "https://registry.yarnpkg.com/icojs/-/icojs-0.19.4.tgz#fdbc9e61a0945ed1d331beb358d67f72cf7d78dc"
integrity sha512-86oNepPk2jAmbb96BPeucZI7HoSBobFlXDhhjIbwRb3wkQpvdBO5HO9KtMUNzMFT3qqQZsjLsfW+L0/9Rl9VqA== integrity sha512-86oNepPk2jAmbb96BPeucZI7HoSBobFlXDhhjIbwRb3wkQpvdBO5HO9KtMUNzMFT3qqQZsjLsfW+L0/9Rl9VqA==