mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 05:24:55 +03:00
Merge branch 'main' of https://github.com/hydralauncher/hydra
This commit is contained in:
commit
74d3aa10cb
@ -2,3 +2,4 @@ node_modules
|
||||
dist
|
||||
out
|
||||
.gitignore
|
||||
migration.stub
|
||||
|
@ -5,4 +5,3 @@ pnpm-lock.yaml
|
||||
LICENSE.md
|
||||
tsconfig.json
|
||||
tsconfig.*.json
|
||||
src/main/migrations
|
||||
|
68
SECURITY.md
Normal file
68
SECURITY.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Security Policy
|
||||
|
||||
## Purpose of the Policy
|
||||
|
||||
The purpose of this Security Policy is to ensure the security of our project and maintain the trust of the community.
|
||||
|
||||
## Who is Affected by the Policy
|
||||
|
||||
This policy applies to all members of our project community, including developers, testers, repository administrators, and users.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.0.x | :white_check_mark: |
|
||||
| < 1.2.0 | :x: |
|
||||
|
||||
## Development Recommendations
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Follow secure coding principles.
|
||||
- Use well-established libraries and frameworks.
|
||||
- Regularly update dependencies.
|
||||
- Conduct thorough testing, including security-related tests.
|
||||
|
||||
### Unrecommended Practices
|
||||
|
||||
- Do not use known vulnerabilities that have not been patched.
|
||||
- Do not publish sensitive information such as API keys or passwords.
|
||||
- Do not vote for changes that degrade the security of the project.
|
||||
|
||||
### User-Generated Content
|
||||
|
||||
- Ensure that user-generated content does not contain hidden threats.
|
||||
- Be cautious when handling user data.
|
||||
|
||||
### Community Interaction
|
||||
|
||||
- Treat each other with respect and politeness.
|
||||
- Do not spread spam or spam bots.
|
||||
- Follow community guidelines.
|
||||
|
||||
### Vulnerability Discovery and Reporting
|
||||
|
||||
- If you discover a vulnerability, report it as an issue on GitHub.
|
||||
- Your report should contain detailed information about the vulnerability, including steps to resolve it.
|
||||
|
||||
### Reporting Method
|
||||
|
||||
To report a vulnerability, create a new issue on GitHub and use branch isolation to provide details about the vulnerability.
|
||||
|
||||
### Details to Provide
|
||||
|
||||
Please provide the following information about the vulnerability:
|
||||
|
||||
- Description of the vulnerability
|
||||
- Steps to resolve the vulnerability
|
||||
- Versions on which the vulnerability was found
|
||||
- Code examples illustrating the vulnerability (if it is safe to do so)
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
- If we accept the reported vulnerability, we will release a patch and update the security information on GitHub.
|
||||
- If we reject the reported vulnerability, we will provide an explanation.
|
@ -29,7 +29,7 @@
|
||||
"build:mac": "electron-vite build && electron-builder --mac",
|
||||
"build:linux": "electron-vite build && electron-builder --linux",
|
||||
"prepare": "husky",
|
||||
"typeorm:migration-create": "yarn typeorm migration:create"
|
||||
"knex:migrate:make": "knex --knexfile src/main/knexfile.ts migrate:make --esm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cospired/i18n-iso-languages": "^4.2.0",
|
||||
@ -44,7 +44,7 @@
|
||||
"@vanilla-extract/recipes": "^0.5.2",
|
||||
"auto-launch": "^5.0.6",
|
||||
"axios": "^1.6.8",
|
||||
"better-sqlite3": "^9.5.0",
|
||||
"better-sqlite3": "^11.2.1",
|
||||
"check-disk-space": "^3.4.0",
|
||||
"classnames": "^2.5.1",
|
||||
"color": "^4.2.3",
|
||||
@ -62,6 +62,7 @@
|
||||
"iso-639-1": "3.1.2",
|
||||
"jsdom": "^24.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"knex": "^3.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lottie-react": "^2.4.0",
|
||||
"parse-torrent": "^11.0.16",
|
||||
@ -107,6 +108,7 @@
|
||||
"prettier": "^3.2.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.12",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
|
@ -270,6 +270,7 @@
|
||||
"pending": "Pending",
|
||||
"no_pending_invites": "You have no pending invites",
|
||||
"no_blocked_users": "You have no blocked users",
|
||||
"friend_code_copied": "Friend code copied"
|
||||
"friend_code_copied": "Friend code copied",
|
||||
"undo_friendship_modal_text": "This will undo your friendship with {{displayName}}"
|
||||
}
|
||||
}
|
||||
|
@ -273,6 +273,7 @@
|
||||
"pending": "Pendentes",
|
||||
"no_pending_invites": "Você não possui convites de amizade pendentes",
|
||||
"no_blocked_users": "Você não tem nenhum usuário bloqueado",
|
||||
"friend_code_copied": "Código de amigo copiado"
|
||||
"friend_code_copied": "Código de amigo copiado",
|
||||
"undo_friendship_modal_text": "Isso irá remover sua amizade com {{displayName}}"
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
} from "@main/entity";
|
||||
|
||||
import { databasePath } from "./constants";
|
||||
import * as migrations from "./migrations";
|
||||
|
||||
export const dataSource = new DataSource({
|
||||
type: "better-sqlite3",
|
||||
@ -23,7 +22,6 @@ export const dataSource = new DataSource({
|
||||
DownloadQueue,
|
||||
UserAuth,
|
||||
],
|
||||
synchronize: true,
|
||||
synchronize: false,
|
||||
database: databasePath,
|
||||
migrations,
|
||||
});
|
||||
|
@ -22,12 +22,6 @@ export class Repack {
|
||||
@Column("text", { unique: true })
|
||||
magnet: string;
|
||||
|
||||
/**
|
||||
* @deprecated Direct scraping capability has been removed
|
||||
*/
|
||||
@Column("int", { nullable: true })
|
||||
page: number;
|
||||
|
||||
@Column("text")
|
||||
repacker: string;
|
||||
|
||||
|
BIN
src/main/hydra.dev.db
Normal file
BIN
src/main/hydra.dev.db
Normal file
Binary file not shown.
@ -9,6 +9,7 @@ import { logger, PythonInstance, WindowManager } from "@main/services";
|
||||
import { dataSource } from "@main/data-source";
|
||||
import * as resources from "@locales";
|
||||
import { userPreferencesRepository } from "@main/repository";
|
||||
import { knexClient, migrationConfig } from "./knex-client";
|
||||
|
||||
const { autoUpdater } = updater;
|
||||
|
||||
@ -52,6 +53,18 @@ if (process.defaultApp) {
|
||||
app.setAsDefaultProtocolClient(PROTOCOL);
|
||||
}
|
||||
|
||||
const runMigrations = async () => {
|
||||
await knexClient.migrate.list(migrationConfig).then((result) => {
|
||||
logger.log(
|
||||
"Migrations to run:",
|
||||
result[1].map((migration) => migration.name)
|
||||
);
|
||||
});
|
||||
|
||||
await knexClient.migrate.latest(migrationConfig);
|
||||
await knexClient.destroy();
|
||||
};
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
@ -63,8 +76,15 @@ app.whenReady().then(async () => {
|
||||
return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString());
|
||||
});
|
||||
|
||||
await runMigrations()
|
||||
.then(() => {
|
||||
logger.log("Migrations executed successfully");
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.log("Migrations failed to run:", err);
|
||||
});
|
||||
|
||||
await dataSource.initialize();
|
||||
await dataSource.runMigrations();
|
||||
|
||||
await import("./main");
|
||||
|
||||
@ -86,10 +106,15 @@ app.on("browser-window-created", (_, window) => {
|
||||
|
||||
const handleDeepLinkPath = (uri?: string) => {
|
||||
if (!uri) return;
|
||||
const url = new URL(uri);
|
||||
|
||||
if (url.host === "install-source") {
|
||||
WindowManager.redirect(`settings${url.search}`);
|
||||
try {
|
||||
const url = new URL(uri);
|
||||
|
||||
if (url.host === "install-source") {
|
||||
WindowManager.redirect(`settings${url.search}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error handling deep link", uri, error);
|
||||
}
|
||||
};
|
||||
|
||||
|
29
src/main/knex-client.ts
Normal file
29
src/main/knex-client.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import knex, { Knex } from "knex";
|
||||
import { databasePath } from "./constants";
|
||||
import { Hydra2_0_3 } from "./migrations/20240830143811_Hydra_2_0_3";
|
||||
import { RepackUris } from "./migrations/20240830143906_RepackUris";
|
||||
|
||||
export type HydraMigration = Knex.Migration & { name: string };
|
||||
|
||||
class MigrationSource implements Knex.MigrationSource<HydraMigration> {
|
||||
getMigrations(): Promise<HydraMigration[]> {
|
||||
return Promise.resolve([Hydra2_0_3, RepackUris]);
|
||||
}
|
||||
getMigrationName(migration: HydraMigration): string {
|
||||
return migration.name;
|
||||
}
|
||||
getMigration(migration: HydraMigration): Promise<Knex.Migration> {
|
||||
return Promise.resolve(migration);
|
||||
}
|
||||
}
|
||||
|
||||
export const knexClient = knex({
|
||||
client: "better-sqlite3",
|
||||
connection: {
|
||||
filename: databasePath,
|
||||
},
|
||||
});
|
||||
|
||||
export const migrationConfig: Knex.MigratorConfig = {
|
||||
migrationSource: new MigrationSource(),
|
||||
};
|
10
src/main/knexfile.ts
Normal file
10
src/main/knexfile.ts
Normal file
@ -0,0 +1,10 @@
|
||||
const config = {
|
||||
development: {
|
||||
migrations: {
|
||||
extension: "ts",
|
||||
stub: "migrations/migration.stub",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
@ -1,50 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Hydra2031724081695967 implements MigrationInterface {
|
||||
name = 'Hydra2031724081695967'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "game" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "objectID" text NOT NULL, "remoteId" text, "title" text NOT NULL, "iconUrl" text, "folderName" text, "downloadPath" text, "executablePath" text, "playTimeInMilliseconds" integer NOT NULL DEFAULT (0), "shop" text NOT NULL, "status" text, "downloader" integer NOT NULL DEFAULT (1), "progress" float NOT NULL DEFAULT (0), "bytesDownloaded" integer NOT NULL DEFAULT (0), "lastTimePlayed" datetime, "fileSize" float NOT NULL DEFAULT (0), "uri" text, "isDeleted" boolean NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "repackId" integer, CONSTRAINT "UQ_04293f46e8db3deaec8dfb69264" UNIQUE ("objectID"), CONSTRAINT "UQ_6dac8c3148e141251a4864e94d4" UNIQUE ("remoteId"), CONSTRAINT "REL_0c1d6445ad047d9bbd256f961f" UNIQUE ("repackId"))`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "download_source" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "url" text, "name" text NOT NULL, "etag" text, "downloadCount" integer NOT NULL DEFAULT (0), "status" text NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "UQ_aec2879321a87e9bb2ed477981a" UNIQUE ("url"))`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"))`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "user_preferences" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "downloadsPath" text, "language" text NOT NULL DEFAULT ('en'), "realDebridApiToken" text, "downloadNotificationsEnabled" boolean NOT NULL DEFAULT (0), "repackUpdatesNotificationsEnabled" boolean NOT NULL DEFAULT (0), "preferQuitInsteadOfHiding" boolean NOT NULL DEFAULT (0), "runAtStartup" boolean NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "game_shop_cache" ("objectID" text PRIMARY KEY NOT NULL, "shop" text NOT NULL, "serializedData" text, "howLongToBeatSerializedData" text, "language" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "download_queue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "gameId" integer, CONSTRAINT "REL_aed852c94d9ded617a7a07f541" UNIQUE ("gameId"))`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "user_auth" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "userId" text NOT NULL DEFAULT (''), "displayName" text NOT NULL DEFAULT (''), "profileImageUrl" text, "accessToken" text NOT NULL DEFAULT (''), "refreshToken" text NOT NULL DEFAULT (''), "tokenExpirationTimestamp" integer NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "temporary_game" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "objectID" text NOT NULL, "remoteId" text, "title" text NOT NULL, "iconUrl" text, "folderName" text, "downloadPath" text, "executablePath" text, "playTimeInMilliseconds" integer NOT NULL DEFAULT (0), "shop" text NOT NULL, "status" text, "downloader" integer NOT NULL DEFAULT (1), "progress" float NOT NULL DEFAULT (0), "bytesDownloaded" integer NOT NULL DEFAULT (0), "lastTimePlayed" datetime, "fileSize" float NOT NULL DEFAULT (0), "uri" text, "isDeleted" boolean NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "repackId" integer, CONSTRAINT "UQ_04293f46e8db3deaec8dfb69264" UNIQUE ("objectID"), CONSTRAINT "UQ_6dac8c3148e141251a4864e94d4" UNIQUE ("remoteId"), CONSTRAINT "REL_0c1d6445ad047d9bbd256f961f" UNIQUE ("repackId"), CONSTRAINT "FK_0c1d6445ad047d9bbd256f961f6" FOREIGN KEY ("repackId") REFERENCES "repack" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_game"("id", "objectID", "remoteId", "title", "iconUrl", "folderName", "downloadPath", "executablePath", "playTimeInMilliseconds", "shop", "status", "downloader", "progress", "bytesDownloaded", "lastTimePlayed", "fileSize", "uri", "isDeleted", "createdAt", "updatedAt", "repackId") SELECT "id", "objectID", "remoteId", "title", "iconUrl", "folderName", "downloadPath", "executablePath", "playTimeInMilliseconds", "shop", "status", "downloader", "progress", "bytesDownloaded", "lastTimePlayed", "fileSize", "uri", "isDeleted", "createdAt", "updatedAt", "repackId" FROM "game"`);
|
||||
await queryRunner.query(`DROP TABLE "game"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_game" RENAME TO "game"`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "temporary_repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"), CONSTRAINT "FK_13f131029be1dd361fd3cd9c2a6" FOREIGN KEY ("downloadSourceId") REFERENCES "download_source" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_repack"("id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "repack"`);
|
||||
await queryRunner.query(`DROP TABLE "repack"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_repack" RENAME TO "repack"`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "temporary_download_queue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "gameId" integer, CONSTRAINT "REL_aed852c94d9ded617a7a07f541" UNIQUE ("gameId"), CONSTRAINT "FK_aed852c94d9ded617a7a07f5415" FOREIGN KEY ("gameId") REFERENCES "game" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_download_queue"("id", "createdAt", "updatedAt", "gameId") SELECT "id", "createdAt", "updatedAt", "gameId" FROM "download_queue"`);
|
||||
await queryRunner.query(`DROP TABLE "download_queue"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_download_queue" RENAME TO "download_queue"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "download_queue" RENAME TO "temporary_download_queue"`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "download_queue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "gameId" integer, CONSTRAINT "REL_aed852c94d9ded617a7a07f541" UNIQUE ("gameId"))`);
|
||||
await queryRunner.query(`INSERT INTO "download_queue"("id", "createdAt", "updatedAt", "gameId") SELECT "id", "createdAt", "updatedAt", "gameId" FROM "temporary_download_queue"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_download_queue"`);
|
||||
await queryRunner.query(`ALTER TABLE "repack" RENAME TO "temporary_repack"`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"))`);
|
||||
await queryRunner.query(`INSERT INTO "repack"("id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "temporary_repack"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_repack"`);
|
||||
await queryRunner.query(`ALTER TABLE "game" RENAME TO "temporary_game"`);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "game" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "objectID" text NOT NULL, "remoteId" text, "title" text NOT NULL, "iconUrl" text, "folderName" text, "downloadPath" text, "executablePath" text, "playTimeInMilliseconds" integer NOT NULL DEFAULT (0), "shop" text NOT NULL, "status" text, "downloader" integer NOT NULL DEFAULT (1), "progress" float NOT NULL DEFAULT (0), "bytesDownloaded" integer NOT NULL DEFAULT (0), "lastTimePlayed" datetime, "fileSize" float NOT NULL DEFAULT (0), "uri" text, "isDeleted" boolean NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "repackId" integer, CONSTRAINT "UQ_04293f46e8db3deaec8dfb69264" UNIQUE ("objectID"), CONSTRAINT "UQ_6dac8c3148e141251a4864e94d4" UNIQUE ("remoteId"), CONSTRAINT "REL_0c1d6445ad047d9bbd256f961f" UNIQUE ("repackId"))`);
|
||||
await queryRunner.query(`INSERT INTO "game"("id", "objectID", "remoteId", "title", "iconUrl", "folderName", "downloadPath", "executablePath", "playTimeInMilliseconds", "shop", "status", "downloader", "progress", "bytesDownloaded", "lastTimePlayed", "fileSize", "uri", "isDeleted", "createdAt", "updatedAt", "repackId") SELECT "id", "objectID", "remoteId", "title", "iconUrl", "folderName", "downloadPath", "executablePath", "playTimeInMilliseconds", "shop", "status", "downloader", "progress", "bytesDownloaded", "lastTimePlayed", "fileSize", "uri", "isDeleted", "createdAt", "updatedAt", "repackId" FROM "temporary_game"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_game"`);
|
||||
await queryRunner.query(`DROP TABLE "user_auth"`);
|
||||
await queryRunner.query(`DROP TABLE "download_queue"`);
|
||||
await queryRunner.query(`DROP TABLE "game_shop_cache"`);
|
||||
await queryRunner.query(`DROP TABLE "user_preferences"`);
|
||||
await queryRunner.query(`DROP TABLE "repack"`);
|
||||
await queryRunner.query(`DROP TABLE "download_source"`);
|
||||
await queryRunner.query(`DROP TABLE "game"`);
|
||||
}
|
||||
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class DowloadsRefactor1724081984535 implements MigrationInterface {
|
||||
name = 'DowloadsRefactor1724081984535'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "temporary_repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, "uris" text NOT NULL DEFAULT ('[]'), CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"), CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "FK_13f131029be1dd361fd3cd9c2a6" FOREIGN KEY ("downloadSourceId") REFERENCES "download_source" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_repack"("id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "repack"`);
|
||||
await queryRunner.query(`DROP TABLE "repack"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_repack" RENAME TO "repack"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "repack" RENAME TO "temporary_repack"`);
|
||||
await queryRunner.query(`CREATE TABLE "repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"), CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "FK_13f131029be1dd361fd3cd9c2a6" FOREIGN KEY ("downloadSourceId") REFERENCES "download_source" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "repack"("id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "temporary_repack"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_repack"`);
|
||||
}
|
||||
|
||||
}
|
171
src/main/migrations/20240830143811_Hydra_2_0_3.ts
Normal file
171
src/main/migrations/20240830143811_Hydra_2_0_3.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import type { HydraMigration } from "@main/knex-client";
|
||||
import type { Knex } from "knex";
|
||||
|
||||
export const Hydra2_0_3: HydraMigration = {
|
||||
name: "Hydra_2_0_3",
|
||||
up: async (knex: Knex) => {
|
||||
const timestamp = new Date().getTime();
|
||||
|
||||
await knex.schema.hasTable("migrations").then(async (exists) => {
|
||||
if (exists) {
|
||||
await knex.schema.dropTable("migrations");
|
||||
}
|
||||
});
|
||||
|
||||
await knex.schema.hasTable("download_source").then(async (exists) => {
|
||||
if (!exists) {
|
||||
await knex.schema.createTable("download_source", (table) => {
|
||||
table.increments("id").primary();
|
||||
table
|
||||
.text("url")
|
||||
.unique({ indexName: "download_source_url_unique_" + timestamp });
|
||||
table.text("name").notNullable();
|
||||
table.text("etag");
|
||||
table.integer("downloadCount").notNullable().defaultTo(0);
|
||||
table.text("status").notNullable().defaultTo(0);
|
||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await knex.schema.hasTable("repack").then(async (exists) => {
|
||||
if (!exists) {
|
||||
await knex.schema.createTable("repack", (table) => {
|
||||
table.increments("id").primary();
|
||||
table
|
||||
.text("title")
|
||||
.notNullable()
|
||||
.unique({ indexName: "repack_title_unique_" + timestamp });
|
||||
table
|
||||
.text("magnet")
|
||||
.notNullable()
|
||||
.unique({ indexName: "repack_magnet_unique_" + timestamp });
|
||||
table.integer("page");
|
||||
table.text("repacker").notNullable();
|
||||
table.text("fileSize").notNullable();
|
||||
table.datetime("uploadDate").notNullable();
|
||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
||||
table
|
||||
.integer("downloadSourceId")
|
||||
.references("download_source.id")
|
||||
.onDelete("CASCADE");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await knex.schema.hasTable("game").then(async (exists) => {
|
||||
if (!exists) {
|
||||
await knex.schema.createTable("game", (table) => {
|
||||
table.increments("id").primary();
|
||||
table
|
||||
.text("objectID")
|
||||
.notNullable()
|
||||
.unique({ indexName: "game_objectID_unique_" + timestamp });
|
||||
table
|
||||
.text("remoteId")
|
||||
.unique({ indexName: "game_remoteId_unique_" + timestamp });
|
||||
table.text("title").notNullable();
|
||||
table.text("iconUrl");
|
||||
table.text("folderName");
|
||||
table.text("downloadPath");
|
||||
table.text("executablePath");
|
||||
table.integer("playTimeInMilliseconds").notNullable().defaultTo(0);
|
||||
table.text("shop").notNullable();
|
||||
table.text("status");
|
||||
table.integer("downloader").notNullable().defaultTo(1);
|
||||
table.float("progress").notNullable().defaultTo(0);
|
||||
table.integer("bytesDownloaded").notNullable().defaultTo(0);
|
||||
table.datetime("lastTimePlayed");
|
||||
table.float("fileSize").notNullable().defaultTo(0);
|
||||
table.text("uri");
|
||||
table.boolean("isDeleted").notNullable().defaultTo(0);
|
||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
||||
table
|
||||
.integer("repackId")
|
||||
.references("repack.id")
|
||||
.unique("repack_repackId_unique_" + timestamp);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await knex.schema.hasTable("user_preferences").then(async (exists) => {
|
||||
if (!exists) {
|
||||
await knex.schema.createTable("user_preferences", (table) => {
|
||||
table.increments("id").primary();
|
||||
table.text("downloadsPath");
|
||||
table.text("language").notNullable().defaultTo("en");
|
||||
table.text("realDebridApiToken");
|
||||
table
|
||||
.boolean("downloadNotificationsEnabled")
|
||||
.notNullable()
|
||||
.defaultTo(0);
|
||||
table
|
||||
.boolean("repackUpdatesNotificationsEnabled")
|
||||
.notNullable()
|
||||
.defaultTo(0);
|
||||
table.boolean("preferQuitInsteadOfHiding").notNullable().defaultTo(0);
|
||||
table.boolean("runAtStartup").notNullable().defaultTo(0);
|
||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await knex.schema.hasTable("game_shop_cache").then(async (exists) => {
|
||||
if (!exists) {
|
||||
await knex.schema.createTable("game_shop_cache", (table) => {
|
||||
table.text("objectID").primary().notNullable();
|
||||
table.text("shop").notNullable();
|
||||
table.text("serializedData");
|
||||
table.text("howLongToBeatSerializedData");
|
||||
table.text("language");
|
||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await knex.schema.hasTable("download_queue").then(async (exists) => {
|
||||
if (!exists) {
|
||||
await knex.schema.createTable("download_queue", (table) => {
|
||||
table.increments("id").primary();
|
||||
table
|
||||
.integer("gameId")
|
||||
.references("game.id")
|
||||
.unique("download_queue_gameId_unique_" + timestamp);
|
||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await knex.schema.hasTable("user_auth").then(async (exists) => {
|
||||
if (!exists) {
|
||||
await knex.schema.createTable("user_auth", (table) => {
|
||||
table.increments("id").primary();
|
||||
table.text("userId").notNullable().defaultTo("");
|
||||
table.text("displayName").notNullable().defaultTo("");
|
||||
table.text("profileImageUrl");
|
||||
table.text("accessToken").notNullable().defaultTo("");
|
||||
table.text("refreshToken").notNullable().defaultTo("");
|
||||
table.integer("tokenExpirationTimestamp").notNullable().defaultTo(0);
|
||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
down: async (knex: Knex) => {
|
||||
await knex.schema.dropTableIfExists("game");
|
||||
await knex.schema.dropTableIfExists("repack");
|
||||
await knex.schema.dropTableIfExists("download_queue");
|
||||
await knex.schema.dropTableIfExists("user_auth");
|
||||
await knex.schema.dropTableIfExists("game_shop_cache");
|
||||
await knex.schema.dropTableIfExists("user_preferences");
|
||||
await knex.schema.dropTableIfExists("download_source");
|
||||
},
|
||||
};
|
58
src/main/migrations/20240830143906_RepackUris.ts
Normal file
58
src/main/migrations/20240830143906_RepackUris.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import type { HydraMigration } from "@main/knex-client";
|
||||
import type { Knex } from "knex";
|
||||
|
||||
export const RepackUris: HydraMigration = {
|
||||
name: "RepackUris",
|
||||
up: async (knex: Knex) => {
|
||||
await knex.schema.createTable("temporary_repack", (table) => {
|
||||
const timestamp = new Date().getTime();
|
||||
table.increments("id").primary();
|
||||
table
|
||||
.text("title")
|
||||
.notNullable()
|
||||
.unique({ indexName: "repack_title_unique_" + timestamp });
|
||||
table
|
||||
.text("magnet")
|
||||
.notNullable()
|
||||
.unique({ indexName: "repack_magnet_unique_" + timestamp });
|
||||
table.text("repacker").notNullable();
|
||||
table.text("fileSize").notNullable();
|
||||
table.datetime("uploadDate").notNullable();
|
||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
||||
table
|
||||
.integer("downloadSourceId")
|
||||
.references("download_source.id")
|
||||
.onDelete("CASCADE");
|
||||
table.text("uris").notNullable().defaultTo("[]");
|
||||
});
|
||||
await knex.raw(
|
||||
`INSERT INTO "temporary_repack"("id", "title", "magnet", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "repack"`
|
||||
);
|
||||
await knex.schema.dropTable("repack");
|
||||
await knex.schema.renameTable("temporary_repack", "repack");
|
||||
},
|
||||
|
||||
down: async (knex: Knex) => {
|
||||
await knex.schema.renameTable("repack", "temporary_repack");
|
||||
await knex.schema.createTable("repack", (table) => {
|
||||
table.increments("id").primary();
|
||||
table.text("title").notNullable().unique();
|
||||
table.text("magnet").notNullable().unique();
|
||||
table.integer("page");
|
||||
table.text("repacker").notNullable();
|
||||
table.text("fileSize").notNullable();
|
||||
table.datetime("uploadDate").notNullable();
|
||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
||||
table
|
||||
.integer("downloadSourceId")
|
||||
.references("download_source.id")
|
||||
.onDelete("CASCADE");
|
||||
});
|
||||
await knex.raw(
|
||||
`INSERT INTO "repack"("id", "title", "magnet", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "temporary_repack"`
|
||||
);
|
||||
await knex.schema.dropTable("temporary_repack");
|
||||
},
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
export * from "./1724081695967-Hydra_2_0_3";
|
||||
export * from "./1724081984535-DowloadsRefactor";
|
11
src/main/migrations/migration.stub
Normal file
11
src/main/migrations/migration.stub
Normal file
@ -0,0 +1,11 @@
|
||||
import type { HydraMigration } from "@main/knex-client";
|
||||
import type { Knex } from "knex";
|
||||
|
||||
export const MigrationName: HydraMigration = {
|
||||
name: "MigrationName",
|
||||
up: async (knex: Knex) => {
|
||||
await knex.schema.createTable("table_name", (table) => {});
|
||||
},
|
||||
|
||||
down: async (knex: Knex) => {},
|
||||
};
|
@ -2,6 +2,7 @@ import axios from "axios";
|
||||
import { requestWebPage } from "@main/helpers";
|
||||
import { HowLongToBeatCategory } from "@types";
|
||||
import { formatName } from "@shared";
|
||||
import { logger } from "./logger";
|
||||
|
||||
export interface HowLongToBeatResult {
|
||||
game_id: number;
|
||||
@ -13,22 +14,27 @@ export interface HowLongToBeatSearchResponse {
|
||||
}
|
||||
|
||||
export const searchHowLongToBeat = async (gameName: string) => {
|
||||
const response = await axios.post(
|
||||
"https://howlongtobeat.com/api/search",
|
||||
{
|
||||
searchType: "games",
|
||||
searchTerms: formatName(gameName).split(" "),
|
||||
searchPage: 1,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
||||
Referer: "https://howlongtobeat.com/",
|
||||
const response = await axios
|
||||
.post(
|
||||
"https://howlongtobeat.com/api/search",
|
||||
{
|
||||
searchType: "games",
|
||||
searchTerms: formatName(gameName).split(" "),
|
||||
searchPage: 1,
|
||||
size: 100,
|
||||
},
|
||||
}
|
||||
);
|
||||
{
|
||||
headers: {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
||||
Referer: "https://howlongtobeat.com/",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((error) => {
|
||||
logger.error("Error searching HowLongToBeat:", error?.response?.status);
|
||||
return { data: { data: [] } };
|
||||
});
|
||||
|
||||
return response.data as HowLongToBeatSearchResponse;
|
||||
};
|
||||
|
@ -104,6 +104,7 @@ export const section = style({
|
||||
alignItems: "center",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
height: "100%",
|
||||
overflow: "hidden",
|
||||
});
|
||||
|
||||
export const backButton = recipe({
|
||||
@ -136,11 +137,15 @@ export const backButton = recipe({
|
||||
export const title = recipe({
|
||||
base: {
|
||||
transition: "all ease 0.2s",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
width: "100%",
|
||||
},
|
||||
variants: {
|
||||
hasBackButton: {
|
||||
true: {
|
||||
transform: "translateX(28px)",
|
||||
width: "calc(100% - 28px)",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -72,7 +72,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
||||
isWindows: window.electron.platform === "win32",
|
||||
})}
|
||||
>
|
||||
<section className={styles.section}>
|
||||
<section className={styles.section} style={{ flex: 1 }}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.backButton({
|
||||
|
@ -7,22 +7,24 @@ export const profileContainerBackground = createVar();
|
||||
export const profileContainer = style({
|
||||
background: profileContainerBackground,
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
cursor: "pointer",
|
||||
":hover": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||
},
|
||||
borderBottom: `solid 1px ${vars.color.border}`,
|
||||
boxShadow: "0px 0px 15px 0px rgb(0 0 0 / 70%)",
|
||||
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
|
||||
});
|
||||
|
||||
export const profileButton = style({
|
||||
display: "flex",
|
||||
cursor: "pointer",
|
||||
transition: "all ease 0.1s",
|
||||
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
|
||||
color: vars.color.muted,
|
||||
borderBottom: `solid 1px ${vars.color.border}`,
|
||||
boxShadow: "0px 0px 15px 0px rgb(0 0 0 / 70%)",
|
||||
width: "100%",
|
||||
zIndex: "10",
|
||||
overflow: "hidden",
|
||||
});
|
||||
|
||||
export const profileButtonContent = style({
|
||||
@ -75,16 +77,6 @@ export const profileButtonTitle = style({
|
||||
whiteSpace: "nowrap",
|
||||
});
|
||||
|
||||
export const friendRequestContainer = style({
|
||||
position: "absolute",
|
||||
padding: "8px",
|
||||
right: `${SPACING_UNIT}px`,
|
||||
display: "flex",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
alignItems: "center",
|
||||
});
|
||||
|
||||
export const friendRequestButton = style({
|
||||
color: vars.color.success,
|
||||
cursor: "pointer",
|
||||
|
@ -41,6 +41,9 @@ export function SidebarProfile() {
|
||||
return undefined;
|
||||
}, [profileBackground]);
|
||||
|
||||
const showPendingRequests =
|
||||
userDetails && receivedRequests.length > 0 && !gameRunning;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.profileContainer}
|
||||
@ -88,19 +91,17 @@ export function SidebarProfile() {
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
{userDetails && receivedRequests.length > 0 && !gameRunning && (
|
||||
<div className={styles.friendRequestContainer}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.friendRequestButton}
|
||||
onClick={() =>
|
||||
showFriendsModal(UserFriendModalTab.AddFriend, userDetails.id)
|
||||
}
|
||||
>
|
||||
<PersonAddIcon size={24} />
|
||||
{receivedRequests.length}
|
||||
</button>
|
||||
</div>
|
||||
{showPendingRequests && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.friendRequestButton}
|
||||
onClick={() =>
|
||||
showFriendsModal(UserFriendModalTab.AddFriend, userDetails.id)
|
||||
}
|
||||
>
|
||||
<PersonAddIcon size={24} />
|
||||
{receivedRequests.length}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -0,0 +1,40 @@
|
||||
import { Button, Modal } from "@renderer/components";
|
||||
import * as styles from "./user.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export interface UserConfirmUndoFriendshipModalProps {
|
||||
visible: boolean;
|
||||
displayName: string;
|
||||
onConfirm: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function UserConfirmUndoFriendshipModal({
|
||||
visible,
|
||||
displayName,
|
||||
onConfirm,
|
||||
onClose,
|
||||
}: UserConfirmUndoFriendshipModalProps) {
|
||||
const { t } = useTranslation("user_profile");
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={t("sign_out_modal_title")}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={styles.signOutModalContent}>
|
||||
<p>{t("undo_friendship_modal_text", { displayName })}</p>
|
||||
<div className={styles.signOutModalButtonsContainer}>
|
||||
<Button onClick={onConfirm} theme="danger">
|
||||
{t("undo_friendship")}
|
||||
</Button>
|
||||
|
||||
<Button onClick={onClose} theme="primary">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -34,6 +34,7 @@ import { UserProfileSettingsModal } from "./user-profile-settings-modal";
|
||||
import { UserSignOutModal } from "./user-sign-out-modal";
|
||||
import { UserFriendModalTab } from "../shared-modals/user-friend-modal";
|
||||
import { UserBlockModal } from "./user-block-modal";
|
||||
import { UserConfirmUndoFriendshipModal } from "./user-confirm-undo-friendship-modal";
|
||||
|
||||
const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
|
||||
|
||||
@ -68,6 +69,7 @@ export function UserContent({
|
||||
useState(false);
|
||||
const [showSignOutModal, setShowSignOutModal] = useState(false);
|
||||
const [showUserBlockModal, setShowUserBlockModal] = useState(false);
|
||||
const [showUndoFriendshipModal, setShowUndoFriendshipModal] = useState(false);
|
||||
const [currentGame, setCurrentGame] = useState<GameRunning | null>(null);
|
||||
|
||||
const { gameRunning } = useAppSelector((state) => state.gameRunning);
|
||||
@ -213,17 +215,12 @@ export function UserContent({
|
||||
}
|
||||
|
||||
if (userProfile.relation.status === "ACCEPTED") {
|
||||
const userId =
|
||||
userProfile.relation.AId === userDetails?.id
|
||||
? userProfile.relation.BId
|
||||
: userProfile.relation.AId;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
theme="outline"
|
||||
className={styles.cancelRequestButton}
|
||||
onClick={() => handleFriendAction(userId, "UNDO")}
|
||||
onClick={() => setShowUndoFriendshipModal(true)}
|
||||
>
|
||||
<XCircleIcon size={28} /> {t("undo_friendship")}
|
||||
</Button>
|
||||
@ -291,6 +288,13 @@ export function UserContent({
|
||||
displayName={userProfile.displayName}
|
||||
/>
|
||||
|
||||
<UserConfirmUndoFriendshipModal
|
||||
visible={showUndoFriendshipModal}
|
||||
onClose={() => setShowUndoFriendshipModal(false)}
|
||||
onConfirm={() => handleFriendAction(userProfile.id, "UNDO")}
|
||||
displayName={userProfile.displayName}
|
||||
/>
|
||||
|
||||
<section
|
||||
className={styles.profileContentBox}
|
||||
style={{
|
||||
@ -328,7 +332,9 @@ export function UserContent({
|
||||
</div>
|
||||
|
||||
<div className={styles.profileInformation}>
|
||||
<h2 style={{ fontWeight: "bold" }}>{userProfile.displayName}</h2>
|
||||
<h2 className={styles.profileDisplayName}>
|
||||
{userProfile.displayName}
|
||||
</h2>
|
||||
{currentGame && (
|
||||
<div
|
||||
style={{
|
||||
|
@ -126,6 +126,7 @@ export const UserEditProfile = ({
|
||||
value={form.displayName}
|
||||
required
|
||||
minLength={3}
|
||||
maxLength={50}
|
||||
containerProps={{ style: { width: "100%" } }}
|
||||
onChange={(e) => setForm({ ...form, displayName: e.target.value })}
|
||||
/>
|
||||
|
@ -23,6 +23,7 @@ export const profileContentBox = style({
|
||||
|
||||
export const profileAvatarContainer = style({
|
||||
width: "96px",
|
||||
minWidth: "96px",
|
||||
height: "96px",
|
||||
borderRadius: "50%",
|
||||
display: "flex",
|
||||
@ -100,6 +101,14 @@ export const profileInformation = style({
|
||||
alignItems: "flex-start",
|
||||
color: "#c0c1c7",
|
||||
zIndex: 1,
|
||||
overflow: "hidden",
|
||||
});
|
||||
|
||||
export const profileDisplayName = style({
|
||||
fontWeight: "bold",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
width: "100%",
|
||||
});
|
||||
|
||||
export const profileContent = style({
|
||||
|
86
yarn.lock
86
yarn.lock
@ -2875,10 +2875,10 @@ bep53-range@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/bep53-range/-/bep53-range-2.0.0.tgz"
|
||||
integrity sha512-sMm2sV5PRs0YOVk0LTKtjuIprVzxgTQUsrGX/7Yph2Rm4FO2Fqqtq7hNjsOB5xezM4v4+5rljCgK++UeQJZguA==
|
||||
|
||||
better-sqlite3@^9.5.0:
|
||||
version "9.6.0"
|
||||
resolved "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz"
|
||||
integrity sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==
|
||||
better-sqlite3@^11.2.1:
|
||||
version "11.2.1"
|
||||
resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-11.2.1.tgz#3c6b8a8e2e12444d380e811796b59c8aba012e03"
|
||||
integrity sha512-Xbt1d68wQnUuFIEVsbt6V+RG30zwgbtCGQ4QOcXVrOH0FE4eHk64FWZ9NUfRHS4/x1PXqwz/+KOrnXD7f0WieA==
|
||||
dependencies:
|
||||
bindings "^1.5.0"
|
||||
prebuild-install "^7.1.1"
|
||||
@ -3250,6 +3250,11 @@ color@^4.2.3:
|
||||
color-convert "^2.0.1"
|
||||
color-string "^1.9.0"
|
||||
|
||||
colorette@2.0.19:
|
||||
version "2.0.19"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
|
||||
integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
|
||||
@ -3257,6 +3262,11 @@ combined-stream@^1.0.8:
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
|
||||
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
|
||||
|
||||
commander@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz"
|
||||
@ -3467,7 +3477,7 @@ dayjs@^1.11.9:
|
||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz"
|
||||
integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||
debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
@ -4106,6 +4116,11 @@ eslint@^8.56.0:
|
||||
strip-ansi "^6.0.1"
|
||||
text-table "^0.2.0"
|
||||
|
||||
esm@^3.2.25:
|
||||
version "3.2.25"
|
||||
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
|
||||
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
|
||||
|
||||
espree@^9.6.0, espree@^9.6.1:
|
||||
version "9.6.1"
|
||||
resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz"
|
||||
@ -4468,6 +4483,11 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@
|
||||
has-symbols "^1.0.3"
|
||||
hasown "^2.0.0"
|
||||
|
||||
get-package-type@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
||||
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
|
||||
|
||||
get-stdin@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz"
|
||||
@ -4494,6 +4514,11 @@ get-symbol-description@^1.0.2:
|
||||
es-errors "^1.3.0"
|
||||
get-intrinsic "^1.2.4"
|
||||
|
||||
getopts@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4"
|
||||
integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==
|
||||
|
||||
git-raw-commits@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz"
|
||||
@ -4914,6 +4939,11 @@ internal-slot@^1.0.7:
|
||||
hasown "^2.0.0"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
interpret@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
|
||||
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
|
||||
|
||||
is-array-buffer@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz"
|
||||
@ -5355,6 +5385,26 @@ keyv@^4.0.0, keyv@^4.5.3:
|
||||
dependencies:
|
||||
json-buffer "3.0.1"
|
||||
|
||||
knex@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/knex/-/knex-3.1.0.tgz#b6ddd5b5ad26a6315234a5b09ec38dc4a370bd8c"
|
||||
integrity sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==
|
||||
dependencies:
|
||||
colorette "2.0.19"
|
||||
commander "^10.0.0"
|
||||
debug "4.3.4"
|
||||
escalade "^3.1.1"
|
||||
esm "^3.2.25"
|
||||
get-package-type "^0.1.0"
|
||||
getopts "2.3.0"
|
||||
interpret "^2.2.0"
|
||||
lodash "^4.17.21"
|
||||
pg-connection-string "2.6.2"
|
||||
rechoir "^0.8.0"
|
||||
resolve-from "^5.0.0"
|
||||
tarn "^3.0.2"
|
||||
tildify "2.0.0"
|
||||
|
||||
language-subtag-registry@^0.3.20:
|
||||
version "0.3.22"
|
||||
resolved "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz"
|
||||
@ -5494,7 +5544,7 @@ lodash.upperfirst@^4.3.1:
|
||||
resolved "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz"
|
||||
integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==
|
||||
|
||||
lodash@^4.17.15:
|
||||
lodash@^4.17.15, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@ -6119,6 +6169,11 @@ pend@~1.2.0:
|
||||
resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
pg-connection-string@2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475"
|
||||
integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==
|
||||
|
||||
pg-int8@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
|
||||
@ -6462,6 +6517,13 @@ readdirp@~3.6.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
rechoir@^0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22"
|
||||
integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==
|
||||
dependencies:
|
||||
resolve "^1.20.0"
|
||||
|
||||
redux-thunk@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz"
|
||||
@ -6559,7 +6621,7 @@ resolve-from@^5.0.0:
|
||||
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz"
|
||||
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
|
||||
|
||||
resolve@^1.22.1:
|
||||
resolve@^1.20.0, resolve@^1.22.1:
|
||||
version "1.22.8"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
|
||||
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
|
||||
@ -7088,6 +7150,11 @@ tar@^6.1.12:
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
tarn@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693"
|
||||
integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==
|
||||
|
||||
temp-file@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz"
|
||||
@ -7125,6 +7192,11 @@ thenify-all@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
|
||||
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
|
||||
|
||||
tildify@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a"
|
||||
integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==
|
||||
|
||||
tiny-typed-emitter@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user