mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 21:44:55 +03:00
feat: create UserSubscription
This commit is contained in:
parent
8ff925fbb9
commit
fe681c3af9
@ -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,
|
||||||
|
@ -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";
|
|
||||||
|
@ -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;
|
||||||
|
|
42
src/main/entity/user-subscription.entity.ts
Normal file
42
src/main/entity/user-subscription.entity.ts
Normal 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;
|
||||||
|
}
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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");
|
||||||
|
},
|
||||||
|
};
|
@ -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) => {},
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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 },
|
||||||
|
@ -22,11 +22,15 @@ const saveAchievementsOnLocal = async (
|
|||||||
},
|
},
|
||||||
["objectId", "shop"]
|
["objectId", "shop"]
|
||||||
)
|
)
|
||||||
.then(async () => {
|
.then(() => {
|
||||||
|
return getGameAchievements(objectId, shop as GameShop)
|
||||||
|
.then((achievements) => {
|
||||||
WindowManager.mainWindow?.webContents.send(
|
WindowManager.mainWindow?.webContents.send(
|
||||||
`on-update-achievements-${objectId}-${shop}`,
|
`on-update-achievements-${objectId}-${shop}`,
|
||||||
await getGameAchievements(objectId, shop as GameShop)
|
achievements
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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 {
|
||||||
|
@ -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==
|
||||||
|
Loading…
Reference in New Issue
Block a user