Changes (/shrug)
* Added middleware timer for performance debugging * Relationships and avatar database keys * CDN * Profiles are SelfAccounts in most cases, rather than Accounts * Simplified profile content management * Progression fixes * Relationships (favorites not yet implemented) * Relationship backend * Relationship and avatar routes
This commit is contained in:
@@ -338,4 +338,12 @@ export function LoginLock(rq: express.Request<NoBody, NoBody, { LoginLock: strin
|
||||
|
||||
export type NoBody = Record<string | number | symbol, never>;
|
||||
|
||||
export function startTimer(rq: express.Request, rs: express.Response, nxt: express.NextFunction) {
|
||||
rs.locals.timer = performance.now();
|
||||
nxt();
|
||||
}
|
||||
export function stopTimer(_rq: express.Request, rs: express.Response) {
|
||||
log.n(`(${rs.locals.reqId.substring(0, 11)}) Middleware took ${(performance.now() - rs.locals.timer).toString().substring(0, 6)} ms`);
|
||||
}
|
||||
|
||||
export * as APIUtils from "./apiutils.ts";
|
||||
|
||||
46
src/data/profile/avatar.ts
Normal file
46
src/data/profile/avatar.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Redis } from "../../db.ts";
|
||||
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
|
||||
|
||||
interface AvatarSettings {
|
||||
OutfitSelections: string,
|
||||
HairColor: string,
|
||||
SkinColor: string,
|
||||
FaceFeatures: string
|
||||
}
|
||||
|
||||
export class ProfileAvatarManager extends ProfileContentManager {
|
||||
|
||||
#rootKey = Redis.buildKey(
|
||||
Redis.KeyGroups.Profiles.Root,
|
||||
this.profileId.toString(),
|
||||
Redis.KeyGroups.Profiles.Avatar.Root
|
||||
);
|
||||
|
||||
async setAvatar(settings: AvatarSettings) {
|
||||
const keys = Redis.KeyGroups.Profiles.Avatar;
|
||||
await Promise.all([
|
||||
Redis.Database.set(Redis.buildKey(this.#rootKey, keys.Outfit), settings.OutfitSelections),
|
||||
Redis.Database.set(Redis.buildKey(this.#rootKey, keys.Hair), settings.HairColor),
|
||||
Redis.Database.set(Redis.buildKey(this.#rootKey, keys.Skin), settings.SkinColor),
|
||||
Redis.Database.set(Redis.buildKey(this.#rootKey, keys.Face), settings.FaceFeatures),
|
||||
]);
|
||||
}
|
||||
|
||||
async getAvatar() {
|
||||
const keys = Redis.KeyGroups.Profiles.Avatar;
|
||||
const [outfit, hair, skin, face] = await Promise.all([
|
||||
Redis.Database.get(Redis.buildKey(this.#rootKey, keys.Outfit)),
|
||||
Redis.Database.get(Redis.buildKey(this.#rootKey, keys.Hair)),
|
||||
Redis.Database.get(Redis.buildKey(this.#rootKey, keys.Skin)),
|
||||
Redis.Database.get(Redis.buildKey(this.#rootKey, keys.Face))
|
||||
]);
|
||||
|
||||
return {
|
||||
OutfitSelections: outfit ?? "",
|
||||
HairColor: hair ?? "",
|
||||
SkinColor: skin ?? "",
|
||||
FaceFeatures: face ?? ""
|
||||
} as AvatarSettings;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,9 @@
|
||||
export class ProfileContentManager {
|
||||
|
||||
profileNotSetError = new Error("The profile on this manager is not set.");
|
||||
|
||||
profileId: number | null = null;
|
||||
|
||||
setProfile(id: number) {
|
||||
this.profileId = id;
|
||||
constructor(profileId: number) {
|
||||
this.profileId = profileId;
|
||||
}
|
||||
|
||||
profileIsSet() {
|
||||
return this.profileId !== null;
|
||||
}
|
||||
profileId: number;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
import { Config } from "../../config.ts";
|
||||
import { GameConfigs } from "../config.ts";
|
||||
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
|
||||
import { Redis } from "../../db.ts";
|
||||
@@ -10,26 +9,12 @@ const config = GameConfigs.getConfig();
|
||||
|
||||
export class ProfileProgressionManager extends ProfileContentManager {
|
||||
|
||||
async #getNextLevelRequiredXp() {
|
||||
const xp = await this.getXp();
|
||||
if (typeof config?.LevelProgressionMaps == 'undefined') return null;
|
||||
for (const item of config?.LevelProgressionMaps) {
|
||||
if (xp >= item.RequiredXp) {
|
||||
|
||||
const next = config?.LevelProgressionMaps[config?.LevelProgressionMaps.indexOf(item) + 1];
|
||||
if (typeof next == 'undefined') return null;
|
||||
else return next.RequiredXp;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the profile's exact # of XP
|
||||
* @returns The new # of XP
|
||||
*/
|
||||
async setXp(xp: number) {
|
||||
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Xp), xp.toString());
|
||||
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Xp), xp.toString());
|
||||
return xp;
|
||||
}
|
||||
|
||||
@@ -56,7 +41,7 @@ export class ProfileProgressionManager extends ProfileContentManager {
|
||||
for (const item of config?.LevelProgressionMaps) {
|
||||
if (xp >= item.RequiredXp) {
|
||||
|
||||
const current = config?.LevelProgressionMaps[config?.LevelProgressionMaps.indexOf(item)];
|
||||
const current = config?.LevelProgressionMaps[config?.LevelProgressionMaps.indexOf(item) + 1];
|
||||
if (typeof current == 'undefined') return 1;
|
||||
else return current.Level;
|
||||
|
||||
@@ -65,8 +50,7 @@ export class ProfileProgressionManager extends ProfileContentManager {
|
||||
}
|
||||
|
||||
async getXp() {
|
||||
if (!this.profileIsSet()) throw this.profileNotSetError;
|
||||
let data = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Xp));
|
||||
let data = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Xp));
|
||||
if (data == null) data = (await this.setXp(0)).toString();
|
||||
|
||||
const parsedData = parseInt(data);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Profile } from "../profiles.ts";
|
||||
import { Redis } from "../../db.ts";
|
||||
import UnifiedProfile, { Profile } from "../profiles.ts";
|
||||
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
|
||||
|
||||
enum RelationshipType {
|
||||
@@ -19,7 +20,8 @@ interface Relationship {
|
||||
PlayerID: number, // target player, not this player
|
||||
RelationshipType: RelationshipType,
|
||||
Muted: ReciprocalStatus,
|
||||
Ignored: ReciprocalStatus
|
||||
Ignored: ReciprocalStatus,
|
||||
Favorited?: ReciprocalStatus
|
||||
}
|
||||
|
||||
export class ProfileRelationshipManager extends ProfileContentManager {
|
||||
@@ -29,36 +31,181 @@ export class ProfileRelationshipManager extends ProfileContentManager {
|
||||
PlayerID: 1,
|
||||
RelationshipType: RelationshipType.Friend,
|
||||
Muted: ReciprocalStatus.None,
|
||||
Ignored: ReciprocalStatus.None
|
||||
Ignored: ReciprocalStatus.None,
|
||||
Favorited: ReciprocalStatus.Mutual
|
||||
},
|
||||
{
|
||||
PlayerID: 2,
|
||||
RelationshipType: RelationshipType.Friend,
|
||||
Muted: ReciprocalStatus.None,
|
||||
Ignored: ReciprocalStatus.None
|
||||
Ignored: ReciprocalStatus.None,
|
||||
Favorited: ReciprocalStatus.Mutual
|
||||
}
|
||||
]
|
||||
|
||||
#rootKey = Redis.buildKey(
|
||||
Redis.KeyGroups.Profiles.Root,
|
||||
this.profileId.toString(),
|
||||
Redis.KeyGroups.Profiles.Relationships.Root
|
||||
);
|
||||
|
||||
#ignoringKey = Redis.buildKey(
|
||||
this.#rootKey,
|
||||
Redis.KeyGroups.Profiles.Relationships.Ignoring
|
||||
);
|
||||
|
||||
#mutedKey = Redis.buildKey(
|
||||
this.#rootKey,
|
||||
Redis.KeyGroups.Profiles.Relationships.Muted
|
||||
);
|
||||
|
||||
#friendsKey = Redis.buildKey(
|
||||
this.#rootKey,
|
||||
Redis.KeyGroups.Profiles.Relationships.Friends
|
||||
);
|
||||
|
||||
#incomingFriends = Redis.buildKey(
|
||||
this.#rootKey,
|
||||
Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests
|
||||
);
|
||||
|
||||
#outgoingFriends = Redis.buildKey(
|
||||
this.#rootKey,
|
||||
Redis.KeyGroups.Profiles.Relationships.OutgoingFriendRequests
|
||||
);
|
||||
|
||||
getRelationships() {
|
||||
return this.#baseRelationships; // temporary
|
||||
}
|
||||
|
||||
setPlayerIgnored(player: Profile) {
|
||||
|
||||
async getAllIgnored() {
|
||||
return (await Redis.Database.smembers(this.#ignoringKey)).map(val => parseInt(val)).filter(val => !isNaN(val));
|
||||
}
|
||||
|
||||
setPlayerMuted(player: Profile) {
|
||||
|
||||
async removePlayerIgnored(player: Profile) {
|
||||
await Redis.Database.srem(this.#ignoringKey, player.getId().toString());
|
||||
}
|
||||
|
||||
getPlayerRelationship(player: Profile) {
|
||||
|
||||
async setPlayerIgnored(player: Profile) {
|
||||
await Redis.Database.sadd(this.#ignoringKey, player.getId().toString());
|
||||
}
|
||||
|
||||
sendPlayerFriendRequest(player: Profile) {
|
||||
async getAllMuted() {
|
||||
return (await Redis.Database.smembers(this.#mutedKey)).map(val => parseInt(val)).filter(val => !isNaN(val));
|
||||
}
|
||||
|
||||
async removePlayerMuted(player: Profile) {
|
||||
await Redis.Database.srem(this.#mutedKey, player.getId().toString());
|
||||
}
|
||||
|
||||
async setPlayerMuted(player: Profile) {
|
||||
await Redis.Database.sadd(this.#mutedKey, player.getId().toString());
|
||||
}
|
||||
|
||||
async getAllFriends() {
|
||||
return (await Redis.Database.smembers(this.#friendsKey)).map(val => parseInt(val)).filter(val => !isNaN(val));
|
||||
}
|
||||
|
||||
async #getIncomingRequests() {
|
||||
return (await Redis.Database.smembers(this.#incomingFriends)).map(val => parseInt(val)).filter(val => !isNaN(val));
|
||||
}
|
||||
|
||||
async #getOutgoingRequests() {
|
||||
return (await Redis.Database.smembers(this.#outgoingFriends)).map(val => parseInt(val)).filter(val => !isNaN(val));
|
||||
}
|
||||
|
||||
createRemoteRootKey(remoteProfileId: number) {
|
||||
return Redis.buildKey(
|
||||
Redis.KeyGroups.Profiles.Root,
|
||||
remoteProfileId.toString(),
|
||||
Redis.KeyGroups.Profiles.Relationships.Root
|
||||
);
|
||||
}
|
||||
|
||||
async #clearAssociationWithRemote(remoteProfileId: number) {
|
||||
const remoteRootKey = this.createRemoteRootKey(remoteProfileId);
|
||||
await Redis.Database.srem(this.#incomingFriends, remoteProfileId);
|
||||
await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.OutgoingFriendRequests), this.profileId);
|
||||
await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId);
|
||||
await Redis.Database.srem(this.#outgoingFriends, remoteProfileId);
|
||||
}
|
||||
|
||||
async acceptRequest(remoteProfileId: number) {
|
||||
const requests = await this.#getIncomingRequests();
|
||||
if (!requests.includes(remoteProfileId)) return;
|
||||
|
||||
await this.#clearAssociationWithRemote(remoteProfileId);
|
||||
|
||||
const remoteRootKey = this.createRemoteRootKey(remoteProfileId);
|
||||
|
||||
await Redis.Database.sadd(this.#friendsKey, remoteProfileId);
|
||||
await Redis.Database.sadd(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.Friends), this.profileId);
|
||||
}
|
||||
|
||||
async denyRequest(remoteProfileId: number) {
|
||||
const requests = await this.#getIncomingRequests();
|
||||
if (!requests.includes(remoteProfileId)) return;
|
||||
await this.#clearAssociationWithRemote(remoteProfileId);
|
||||
}
|
||||
|
||||
async getRelationshipType(remoteProfileId: number) {
|
||||
const isFriendsWithRemote = (await Redis.Database.sismember(this.#friendsKey, remoteProfileId)) >= 1;
|
||||
const remoteSentRequest = (await this.#getIncomingRequests()).includes(remoteProfileId);
|
||||
const localSentRequest = (await this.#getOutgoingRequests()).includes(remoteProfileId);
|
||||
|
||||
if (isFriendsWithRemote || (remoteSentRequest && localSentRequest)) return RelationshipType.Friend;
|
||||
else if (remoteSentRequest) return RelationshipType.FriendRequestReceived;
|
||||
else if (localSentRequest) return RelationshipType.FriendRequestSent;
|
||||
else return RelationshipType.None;
|
||||
}
|
||||
|
||||
async getMutedReciprocal(remoteProfileId: number) {
|
||||
const remoteKey = this.createRemoteRootKey(remoteProfileId);
|
||||
|
||||
const localMuted = (await this.getAllMuted()).includes(remoteProfileId);
|
||||
const remoteMuted = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Muted), this.profileId);
|
||||
|
||||
if (localMuted && remoteMuted) return ReciprocalStatus.Mutual;
|
||||
else if (localMuted) return ReciprocalStatus.Local;
|
||||
else if (remoteMuted) return ReciprocalStatus.Remote;
|
||||
else return ReciprocalStatus.None;
|
||||
}
|
||||
|
||||
async getIgnoredReciprocal(remoteProfileId: number) {
|
||||
const remoteKey = this.createRemoteRootKey(remoteProfileId);
|
||||
|
||||
const localIgnored = (await this.getAllMuted()).includes(remoteProfileId);
|
||||
const remoteIgnored = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Ignoring), this.profileId);
|
||||
|
||||
if (localIgnored && remoteIgnored) return ReciprocalStatus.Mutual;
|
||||
else if (localIgnored) return ReciprocalStatus.Local;
|
||||
else if (remoteIgnored) return ReciprocalStatus.Remote;
|
||||
else return ReciprocalStatus.None;
|
||||
}
|
||||
|
||||
async getPlayerRelationship(player: Profile) {
|
||||
const relationship: Relationship = {
|
||||
PlayerID: player.getId(),
|
||||
RelationshipType: await this.getRelationshipType(player.getId()),
|
||||
Muted: await this.getMutedReciprocal(player.getId()),
|
||||
Ignored: await this.getIgnoredReciprocal(player.getId()),
|
||||
}
|
||||
return relationship;
|
||||
}
|
||||
|
||||
async sendPlayerFriendRequest(player: Profile) {
|
||||
await Redis.Database.sadd(Redis.buildKey(this.createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId);
|
||||
await Redis.Database.sadd(this.#outgoingFriends, player.getId());
|
||||
}
|
||||
|
||||
|
||||
async removePlayerFriendRequest(player: Profile) {
|
||||
await Redis.Database.srem(Redis.buildKey(this.createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId);
|
||||
await Redis.Database.srem(this.#outgoingFriends, player.getId());
|
||||
}
|
||||
|
||||
async ignoreAllAssociatedPlatformUsers(platformid: string) {
|
||||
const ids = (await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.PlatformAssociations, platformid))).map(val => parseInt(val)).filter(val => !isNaN(val));
|
||||
for (const id of ids) this.setPlayerIgnored(UnifiedProfile.get(id));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,31 +10,26 @@ export interface Setting {
|
||||
export class ProfileSettingsManager extends ProfileContentManager {
|
||||
|
||||
async getSettings() {
|
||||
if (!this.profileIsSet()) throw this.profileNotSetError;
|
||||
const settings = await Redis.Database.hgetall(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings));
|
||||
const settings = await Redis.Database.hgetall(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings));
|
||||
const returnSettings: Setting[] = [];
|
||||
for (const key of Object.keys(settings)) returnSettings.push({ Key: key, Value: settings[key] });
|
||||
return returnSettings;
|
||||
}
|
||||
|
||||
async getSetting(key: SettingKey) {
|
||||
if (!this.profileIsSet()) throw this.profileNotSetError;
|
||||
return await Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings), key);
|
||||
return await Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key);
|
||||
}
|
||||
|
||||
async setSetting(key: SettingKey, value: string) {
|
||||
if (!this.profileIsSet()) throw this.profileNotSetError;
|
||||
await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
|
||||
await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
|
||||
}
|
||||
|
||||
async delSetting(key: SettingKey) {
|
||||
if (!this.profileIsSet()) throw this.profileNotSetError;
|
||||
await Redis.Database.hdel(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings), key);
|
||||
await Redis.Database.hdel(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key);
|
||||
}
|
||||
|
||||
async delAllSettings() {
|
||||
if (!this.profileIsSet()) throw this.profileNotSetError;
|
||||
await Redis.Database.del(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings));
|
||||
await Redis.Database.del(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { ProfileProgressionManager } from "./profile/progression.ts";
|
||||
import { ProfileReputationManager } from "./profile/reputation.ts";
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
import { ProfileRelationshipManager } from "./profile/relationships.ts";
|
||||
import { ProfileAvatarManager } from "./profile/avatar.ts";
|
||||
|
||||
const config = Config.getConfig();
|
||||
|
||||
@@ -35,6 +36,12 @@ interface AccountExport {
|
||||
username: string;
|
||||
displayName: string;
|
||||
}
|
||||
interface SelfAccountExport extends AccountExport {
|
||||
email?: string,
|
||||
phone?: string,
|
||||
juniorState?: number,
|
||||
parentAccountId?: number
|
||||
}
|
||||
export interface ProfileTokenFormat extends TokenBaseFormat {
|
||||
sub: number;
|
||||
role: "developer" | "user";
|
||||
@@ -120,7 +127,7 @@ class Profile {
|
||||
}
|
||||
|
||||
// surely this can be written better
|
||||
static getExportAccount(id: number): Promise<AccountExport | null> {
|
||||
static getExportAccount(id: number): Promise<SelfAccountExport | null> {
|
||||
return new Promise((resolve, _reject) => {
|
||||
Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Username)).then((val) => {
|
||||
if (val == null) resolve(null);
|
||||
@@ -140,6 +147,7 @@ class Profile {
|
||||
platforms: 1,
|
||||
username: values[2] == null ? "DATABASEERROR" : values[2],
|
||||
displayName: values[3] == null ? (values[2] == null ? "DATABASEERROR" : values[2]) : values[3],
|
||||
email: "" // removes a notification in messages (untested)
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -160,19 +168,20 @@ class Profile {
|
||||
|
||||
#socket: SignalRSocketHandler | null = null;
|
||||
|
||||
Settings = new ProfileSettingsManager();
|
||||
Progression = new ProfileProgressionManager();
|
||||
Reputation = new ProfileReputationManager();
|
||||
Relationships = new ProfileRelationshipManager();
|
||||
Settings: ProfileSettingsManager;
|
||||
Progression: ProfileProgressionManager;
|
||||
Reputation: ProfileReputationManager;
|
||||
Relationships: ProfileRelationshipManager;
|
||||
Avatar: ProfileAvatarManager;
|
||||
|
||||
constructor(id: number) {
|
||||
this.#id = id;
|
||||
|
||||
// Set IDs for all content managers
|
||||
this.Settings.setProfile(this.#id);
|
||||
this.Progression.setProfile(this.#id);
|
||||
this.Reputation.setProfile(this.#id);
|
||||
this.Relationships.setProfile(this.#id);
|
||||
this.Settings = new ProfileSettingsManager(this.#id);
|
||||
this.Progression = new ProfileProgressionManager(this.#id);
|
||||
this.Reputation = new ProfileReputationManager(this.#id);
|
||||
this.Relationships = new ProfileRelationshipManager(this.#id);
|
||||
this.Avatar = new ProfileAvatarManager(this.#id);
|
||||
}
|
||||
|
||||
setInstance(instance: RoomInstance | null) {
|
||||
|
||||
16
src/db.ts
16
src/db.ts
@@ -58,6 +58,7 @@ export const KeyGroups = {
|
||||
Rooms: "rooms",
|
||||
},
|
||||
Profile_Usernames: "profile-usernames",
|
||||
PlatformAssociations: "platforms",
|
||||
Profiles: {
|
||||
Root: "profiles",
|
||||
Username: "username",
|
||||
@@ -68,6 +69,21 @@ export const KeyGroups = {
|
||||
Settings: "settings",
|
||||
DeviceClass: "deviceClass",
|
||||
Xp: "xp",
|
||||
Relationships: {
|
||||
Root: "relationships",
|
||||
IncomingFriendRequests: "incomingFriendRequests",
|
||||
OutgoingFriendRequests: "outgoingFriendRequests",
|
||||
Friends: "friends",
|
||||
Ignoring: "ignore",
|
||||
Muted: "muted",
|
||||
},
|
||||
Avatar: {
|
||||
Root: "avatar",
|
||||
Outfit: "outfit",
|
||||
Hair: "hair",
|
||||
Skin: "skin",
|
||||
Face: "face"
|
||||
}
|
||||
},
|
||||
Operators: "operators",
|
||||
Users: {
|
||||
|
||||
@@ -55,7 +55,8 @@ app.disable("x-powered-by");
|
||||
app.use(
|
||||
(rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
|
||||
rs.setHeader("Instance", instanceId);
|
||||
log.n(`${APIUtils.getSrcIpDefault(rq)} ${rq.method} ${rq.originalUrl}`);
|
||||
rs.locals.reqId = generateRandomString(12);
|
||||
log.n(`${rs.locals.reqId} ${APIUtils.getSrcIpDefault(rq)} ${rq.method} ${rq.originalUrl}`);
|
||||
nxt();
|
||||
},
|
||||
);
|
||||
@@ -78,6 +79,7 @@ const accountRouter = await import("./routes/account.ts");
|
||||
const imgRouter = await import("./routes/img.ts");
|
||||
const matchRouter = await import("./routes/match.ts");
|
||||
const notifyRouter = await import("./socket/route.ts");
|
||||
const cdnRouter = await import("./routes/cdn.ts");
|
||||
|
||||
app.use(nameserverRouter.route.path, nameserverRouter.route.router);
|
||||
app.use(apiRouter.route.path, apiRouter.route.router);
|
||||
@@ -87,6 +89,7 @@ app.use(accountRouter.route.path, accountRouter.route.router);
|
||||
app.use(imgRouter.route.path, imgRouter.route.router);
|
||||
app.use(matchRouter.route.path, matchRouter.route.router);
|
||||
app.use(notifyRouter.route.path, notifyRouter.route.router);
|
||||
app.use(cdnRouter.route.path, cdnRouter.route.router);
|
||||
|
||||
app.use((rq: express.Request, rs: express.Response) => {
|
||||
log.e(`${APIUtils.getSrcIpDefault(rq)} 404 ${rq.method} ${rq.url.toString()}`);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { route as PlayersRoute } from "./api/players.ts"
|
||||
import { route as SettingsRoute } from "./api/settings.ts";
|
||||
import { route as PlayerSubscriptionsRoute } from "./api/playersubscriptions.ts";
|
||||
import { route as PlayerReputationRoute } from "./api/playerReputation.ts";
|
||||
import { route as AvatarRoute } from "./api/avatar.ts";
|
||||
|
||||
export const route = APIUtils.createRouter("/api");
|
||||
|
||||
@@ -21,4 +22,5 @@ route.router.use(RelationshipsRoute.path, RelationshipsRoute.router);
|
||||
route.router.use(PlayersRoute.path, PlayersRoute.router);
|
||||
route.router.use(SettingsRoute.path, SettingsRoute.router);
|
||||
route.router.use(PlayerSubscriptionsRoute.path, PlayerSubscriptionsRoute.router);
|
||||
route.router.use(PlayerReputationRoute.path, PlayerReputationRoute.router);
|
||||
route.router.use(PlayerReputationRoute.path, PlayerReputationRoute.router);
|
||||
route.router.use(AvatarRoute.path, AvatarRoute.router);
|
||||
15
src/routes/api/avatar.ts
Normal file
15
src/routes/api/avatar.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { APIUtils } from "../../apiutils.ts";
|
||||
import { AuthType } from "../../data/users.ts";
|
||||
|
||||
export const route = APIUtils.createRouter("/avatar");
|
||||
|
||||
route.router.get('/v2',
|
||||
|
||||
APIUtils.Authentication,
|
||||
APIUtils.AuthenticationType(AuthType.Game),
|
||||
|
||||
async (_rq, rs) => {
|
||||
rs.json(await rs.locals.profile.Avatar.getAvatar());
|
||||
},
|
||||
|
||||
);
|
||||
@@ -1,5 +1,7 @@
|
||||
import { APIUtils } from "../../apiutils.ts";
|
||||
import { z } from "zod";
|
||||
import { APIUtils, NoBody } from "../../apiutils.ts";
|
||||
import { AuthType } from "../../data/users.ts";
|
||||
import express from "express";
|
||||
|
||||
export const route = APIUtils.createRouter("/relationships");
|
||||
|
||||
@@ -12,4 +14,28 @@ route.router.get('/v2/get',
|
||||
rs.json(rs.locals.profile.Relationships.getRelationships());
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
interface BulkIngorePlatformBody {
|
||||
Platform: string,
|
||||
PlatformIds: string[]
|
||||
}
|
||||
|
||||
const bulkIgnorePlatformSchema = z.object({
|
||||
Platform: z.number(),
|
||||
PlatformIds: z.array(z.string())
|
||||
});
|
||||
|
||||
route.router.post('/v1/bulkignoreplatformusers',
|
||||
|
||||
APIUtils.Authentication,
|
||||
APIUtils.AuthenticationType(AuthType.Game),
|
||||
express.json(),
|
||||
APIUtils.validateRequestBody(bulkIgnorePlatformSchema),
|
||||
|
||||
(rq: express.Request<NoBody, NoBody, BulkIngorePlatformBody>, rs: express.Response) => {
|
||||
for (const id of rq.body.PlatformIds) rs.locals.profile.Relationships.ignoreAllAssociatedPlatformUsers(id);
|
||||
rs.sendStatus(200);
|
||||
},
|
||||
|
||||
);
|
||||
@@ -6,6 +6,7 @@ import { Config } from "../../config.ts";
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
import { z } from "zod";
|
||||
import { AuthType } from "../../data/users.ts";
|
||||
import { Redis } from "../../db.ts";
|
||||
|
||||
const config = Config.getConfig();
|
||||
|
||||
@@ -75,6 +76,7 @@ interface TokenResponseBody {
|
||||
|
||||
route.router.post("/token",
|
||||
|
||||
APIUtils.startTimer,
|
||||
APIUtils.Authentication,
|
||||
APIUtils.AuthenticationType(AuthType.Web),
|
||||
express.urlencoded({ extended: true }),
|
||||
@@ -83,6 +85,7 @@ route.router.post("/token",
|
||||
async (
|
||||
rq: express.Request<NoBody, NoBody, TokenRequestBody>,
|
||||
rs: express.Response<TokenResponseBody>,
|
||||
nxt: express.NextFunction
|
||||
) => {
|
||||
|
||||
function requestFailed(msg: string = "invalid_request") {
|
||||
@@ -146,6 +149,7 @@ route.router.post("/token",
|
||||
|
||||
rs.locals.user.addAssociatedDeviceId(rq.body.device_id);
|
||||
rs.locals.user.addAssociatedPlatformId(rq.body.platform_id);
|
||||
Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.PlatformAssociations, rq.body.platform_id), targetAccount);
|
||||
|
||||
const profile = UnifiedProfile.get(targetAccount);
|
||||
if (!(await Profile.exists(profile.getId()))) {
|
||||
@@ -163,5 +167,9 @@ route.router.post("/token",
|
||||
});
|
||||
|
||||
await profile.setKnownDeviceClass(Number(rq.body.device_class));
|
||||
|
||||
nxt();
|
||||
},
|
||||
|
||||
APIUtils.stopTimer
|
||||
);
|
||||
|
||||
6
src/routes/cdn.ts
Normal file
6
src/routes/cdn.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { APIUtils } from "../apiutils.ts";
|
||||
import { route as ConfigRoute } from "./cdn/config.ts";
|
||||
|
||||
export const route = APIUtils.createRouter("/cdn");
|
||||
|
||||
route.router.use(ConfigRoute.path, ConfigRoute.router);
|
||||
13
src/routes/cdn/config.ts
Normal file
13
src/routes/cdn/config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { APIUtils } from "../../apiutils.ts";
|
||||
|
||||
export const route = APIUtils.createRouter("/config");
|
||||
|
||||
route.router.get('/LoadingScreenTipData',
|
||||
|
||||
APIUtils.Authentication,
|
||||
|
||||
(_rq, rs) => {
|
||||
rs.json([]);
|
||||
}
|
||||
|
||||
);
|
||||
@@ -84,7 +84,6 @@ route.router.post('/heartbeat',
|
||||
async (_rq, rs) => {
|
||||
const pres = await Presence.get(rs.locals.profile);
|
||||
await pres.update();
|
||||
log.d(`pres heartbeat for ${rs.locals.profile.getId()}: ${JSON.stringify(await pres.export())}`);
|
||||
rs.json(await pres.export());
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ export class SignalRSocketHandler {
|
||||
|
||||
destroy(sock: SignalRSocketHandler) {
|
||||
return () => {
|
||||
sock.sendRaw({ type: 7, error: "Socket closed by server" });
|
||||
sock.sendRaw({ type: 7, error: "Socket closed" });
|
||||
sock.#socket.close();
|
||||
sock.#log.i(`Closed hub socket`);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ declare global {
|
||||
profile: Profile;
|
||||
user: User;
|
||||
token: string | undefined;
|
||||
timer: number
|
||||
reqId: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user