Newer
Older
percord / src / util / entities / UserSettingsProtos.ts
/*
	Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
	Copyright (C) 2025 Spacebar and Spacebar Contributors

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Affero General Public License as published
	by the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Affero General Public License for more details.

	You should have received a copy of the GNU Affero General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

import { Column, Entity, JoinColumn, OneToOne, RelationId } from "typeorm";
import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass";
import { dbEngine } from "@spacebar/util";
import { User } from "./User";
import {
	FrecencyUserSettings,
	PreloadedUserSettings,
	PreloadedUserSettings_LaunchPadMode,
	PreloadedUserSettings_SwipeRightToLeftMode,
	PreloadedUserSettings_Theme,
	PreloadedUserSettings_TimestampHourCycle,
	PreloadedUserSettings_UIDensity,
} from "discord-protos";

@Entity({
	name: "user_settings_protos",
	engine: dbEngine,
})
export class UserSettingsProtos extends BaseClassWithoutId {
	@OneToOne(() => User, {
		cascade: true,
		orphanedRowAction: "delete",
		eager: false,
	})
	@JoinColumn({ name: "user_id" })
	user: User;

	@PrimaryIdColumn({ type: "text" })
	user_id: string;

	@Column({ nullable: true, type: String, name: "userSettings" })
	_userSettings: string | undefined;

	@Column({ nullable: true, type: String, name: "frecencySettings" })
	_frecencySettings: string | undefined;

	// @Column({nullable: true, type: "simple-json"})
	// testSettings: {};

	bigintReplacer(_key: string, value: any): any {
		if (typeof value === "bigint") {
			return (value as bigint).toString();
		} else if (value instanceof Uint8Array) {
			return {
				__type: "Uint8Array",
				data: Array.from(value as Uint8Array)
					.map((b) => b.toString(16).padStart(2, "0"))
					.join(""),
			};
		} else {
			return value;
		}
	}

	bigintReviver(_key: string, value: any): any {
		if (typeof value === "string" && /^\d+n$/.test(value)) {
			return BigInt((value as string).slice(0, -1));
		} else if (
			typeof value === "object" &&
			value !== null &&
			"__type" in value
		) {
			if (value.__type === "Uint8Array" && "data" in value) {
				return new Uint8Array(
					value.data
						.match(/.{1,2}/g)!
						.map((byte: string) => parseInt(byte, 16)),
				);
			}
		}
		return value;
	}

	get userSettings(): PreloadedUserSettings | undefined {
		if (!this._userSettings) return undefined;
		return PreloadedUserSettings.fromJson(
			JSON.parse(this._userSettings, this.bigintReviver),
		);
	}

	set userSettings(value: PreloadedUserSettings | undefined) {
		if (value) {
			// this._userSettings = JSON.stringify(value, this.bigintReplacer);
			this._userSettings = PreloadedUserSettings.toJsonString(value);
		} else {
			this._userSettings = undefined;
		}
	}

	get frecencySettings(): FrecencyUserSettings | undefined {
		if (!this._frecencySettings) return undefined;
		return FrecencyUserSettings.fromJson(
			JSON.parse(this._frecencySettings, this.bigintReviver),
		);
	}

	set frecencySettings(value: FrecencyUserSettings | undefined) {
		if (value) {
			this._frecencySettings = JSON.stringify(value, this.bigintReplacer);
		} else {
			this._frecencySettings = undefined;
		}
	}

	static async getOrCreate(user_id: string): Promise<UserSettingsProtos> {
		const user = await User.findOneOrFail({
			where: { id: user_id },
			select: { settings: true },
		});

		let userSettings = await UserSettingsProtos.findOne({
			where: { user_id },
		});

		let modified = false;
		if (!userSettings) {
			userSettings = UserSettingsProtos.create({
				user_id,
			});
			modified = true;
		}

		if (!userSettings.userSettings) {
			userSettings.userSettings = PreloadedUserSettings.create({
				versions: {
					dataVersion: 0,
					clientVersion: 0,
					serverVersion: 0,
				},
			});
			modified = true;
		}

		if (!userSettings.frecencySettings) {
			userSettings.frecencySettings = FrecencyUserSettings.create({
				versions: {
					dataVersion: 0,
					clientVersion: 0,
					serverVersion: 0,
				},
			});
			modified = true;
		}

		if (modified) userSettings = await userSettings.save();

		return userSettings;
	}
}