mirror of
https://github.com/PretendoNetwork/BOSS.git
synced 2026-03-21 17:34:19 -05:00
feat: begin gRPC 2.2.4 port. missing CTR
some of these changes seem like they need database changes as well, that needs to be talked about
This commit is contained in:
parent
22f3016f50
commit
4186968576
33
src/services/grpc/boss/v2/delete-file.ts
Normal file
33
src/services/grpc/boss/v2/delete-file.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getTaskFileByDataID } from '@/database';
|
||||
import { hasPermission } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { DeleteFileRequest } from '@pretendonetwork/grpc/boss/v2/delete_file';
|
||||
import type { Empty } from '@pretendonetwork/grpc/google/protobuf/empty';
|
||||
|
||||
export async function deleteFile(request: DeleteFileRequest, context: CallContext & AuthenticationCallContextExt): Promise<Empty> {
|
||||
if (!hasPermission(context, 'deleteBossFiles')) {
|
||||
throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to delete files');
|
||||
}
|
||||
|
||||
const dataID = request.dataId;
|
||||
const bossAppID = request.bossAppId.trim();
|
||||
|
||||
if (!dataID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing file data ID');
|
||||
}
|
||||
|
||||
const file = await getTaskFileByDataID(dataID);
|
||||
|
||||
if (!file || file.boss_app_id !== bossAppID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `File ${dataID} not found for BOSS app ${bossAppID}`);
|
||||
}
|
||||
|
||||
file.deleted = true;
|
||||
file.updated = BigInt(Date.now());
|
||||
|
||||
await file.save();
|
||||
|
||||
return {};
|
||||
}
|
||||
37
src/services/grpc/boss/v2/delete-task.ts
Normal file
37
src/services/grpc/boss/v2/delete-task.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getTask } from '@/database';
|
||||
import { hasPermission } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { DeleteTaskRequest } from '@pretendonetwork/grpc/boss/v2/delete_task';
|
||||
import type { Empty } from '@pretendonetwork/grpc/google/protobuf/empty';
|
||||
|
||||
export async function deleteTask(request: DeleteTaskRequest, context: CallContext & AuthenticationCallContextExt): Promise<Empty> {
|
||||
if (!hasPermission(context, 'deleteBossTasks')) {
|
||||
throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to delete tasks');
|
||||
}
|
||||
|
||||
const taskID = request.id.trim();
|
||||
const bossAppID = request.bossAppId.trim();
|
||||
|
||||
if (!taskID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing task ID');
|
||||
}
|
||||
|
||||
if (!bossAppID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing BOSS app ID');
|
||||
}
|
||||
|
||||
const task = await getTask(bossAppID, taskID);
|
||||
|
||||
if (!task) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `Task ${taskID} not found for BOSS app ${bossAppID}`);
|
||||
}
|
||||
|
||||
task.deleted = true;
|
||||
task.updated = BigInt(Date.now());
|
||||
|
||||
await task.save();
|
||||
|
||||
return {};
|
||||
}
|
||||
28
src/services/grpc/boss/v2/implementation.ts
Normal file
28
src/services/grpc/boss/v2/implementation.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { listKnownBOSSApps } from '@/services/grpc/boss/v2/list-known-boss-apps';
|
||||
import { listTasks } from '@/services/grpc/boss/v2/list-tasks';
|
||||
import { registerTask } from '@/services/grpc/boss/v2/register-task';
|
||||
import { updateTask } from '@/services/grpc/boss/v2/update-task';
|
||||
import { deleteTask } from '@/services/grpc/boss/v2/delete-task';
|
||||
import { deleteFile } from '@/services/grpc/boss/v2/delete-file';
|
||||
import { listFilesWUP } from '@/services/grpc/boss/v2/list-files-wup';
|
||||
import { uploadFileWUP } from '@/services/grpc/boss/v2/upload-file-wup';
|
||||
import { listFilesCTR } from '@/services/grpc/boss/v2/list-files-ctr';
|
||||
// import { uploadFileCTR } from '@/services/grpc/boss/v2/upload-file-ctr';
|
||||
// import { updateFileMetadataCTR } from '@/services/grpc/boss/v2/update-file-metadata-ctr';
|
||||
import { updateFileMetadataWUP } from '@/services/grpc/boss/v2/update-file-metadata-wup';
|
||||
import type { BossServiceImplementation } from '@pretendonetwork/grpc/boss/v2/boss_service';
|
||||
|
||||
export const bossServiceImplementationV2: BossServiceImplementation = {
|
||||
listKnownBOSSApps,
|
||||
listTasks,
|
||||
registerTask,
|
||||
updateTask,
|
||||
deleteTask,
|
||||
deleteFile,
|
||||
listFilesWUP,
|
||||
uploadFileWUP,
|
||||
listFilesCTR,
|
||||
// uploadFileCTR,
|
||||
// updateFileMetadataCTR,
|
||||
updateFileMetadataWUP
|
||||
};
|
||||
64
src/services/grpc/boss/v2/list-files-ctr.ts
Normal file
64
src/services/grpc/boss/v2/list-files-ctr.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { isValidCountryCode, isValidLanguage } from '@/util';
|
||||
import { getTaskFiles } from '@/database';
|
||||
import type { ListFilesCTRRequest, ListFilesCTRResponse } from '@pretendonetwork/grpc/boss/v2/list_files_ctr';
|
||||
|
||||
const BOSS_APP_ID_FILTER_REGEX = /^[A-Za-z0-9]*$/;
|
||||
|
||||
export async function listFilesCTR(request: ListFilesCTRRequest): Promise<ListFilesCTRResponse> {
|
||||
const taskID = request.taskId.trim();
|
||||
const bossAppID = request.bossAppId.trim();
|
||||
const country = request.country?.trim();
|
||||
const language = request.language?.trim();
|
||||
|
||||
if (!taskID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing task ID');
|
||||
}
|
||||
|
||||
if (!bossAppID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing BOSS app ID');
|
||||
}
|
||||
|
||||
if (bossAppID.length !== 16) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'BOSS app ID must be 16 characters');
|
||||
}
|
||||
|
||||
if (!BOSS_APP_ID_FILTER_REGEX.test(bossAppID)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'BOSS app ID must only contain letters and numbers');
|
||||
}
|
||||
|
||||
if (country && !isValidCountryCode(country)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${country} is not a valid country`);
|
||||
}
|
||||
|
||||
if (language && !isValidLanguage(language)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${language} is not a valid language`);
|
||||
}
|
||||
|
||||
const files = await getTaskFiles(false, bossAppID, taskID, country, language);
|
||||
|
||||
return {
|
||||
files: files.map(file => ({
|
||||
deleted: file.deleted,
|
||||
dataId: file.data_id,
|
||||
taskId: file.task_id,
|
||||
bossAppId: file.boss_app_id,
|
||||
supportedCountries: file.supported_countries,
|
||||
supportedLanguages: file.supported_languages,
|
||||
attributes: {
|
||||
attribute1: file.attribute1,
|
||||
attribute2: file.attribute2,
|
||||
attribute3: file.attribute3,
|
||||
description: file.password
|
||||
},
|
||||
creatorPid: file.creator_pid,
|
||||
name: file.name,
|
||||
hash: file.hash,
|
||||
serialNumber: 0, // TODO - Don't stub this
|
||||
payloadContents: [], // TODO - Don't stub this
|
||||
size: file.size,
|
||||
createdTimestamp: file.created,
|
||||
updatedTimestamp: file.updated
|
||||
}))
|
||||
};
|
||||
}
|
||||
67
src/services/grpc/boss/v2/list-files-wup.ts
Normal file
67
src/services/grpc/boss/v2/list-files-wup.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { isValidCountryCode, isValidLanguage } from '@/util';
|
||||
import { getTaskFiles } from '@/database';
|
||||
import type { ListFilesWUPRequest, ListFilesWUPResponse } from '@pretendonetwork/grpc/boss/v2/list_files_wup';
|
||||
|
||||
const BOSS_APP_ID_FILTER_REGEX = /^[A-Za-z0-9]*$/;
|
||||
|
||||
export async function listFilesWUP(request: ListFilesWUPRequest): Promise<ListFilesWUPResponse> {
|
||||
const taskID = request.taskId.trim();
|
||||
const bossAppID = request.bossAppId.trim();
|
||||
const country = request.country?.trim();
|
||||
const language = request.language?.trim();
|
||||
|
||||
if (!taskID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing task ID');
|
||||
}
|
||||
|
||||
if (!bossAppID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing BOSS app ID');
|
||||
}
|
||||
|
||||
if (bossAppID.length !== 16) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'BOSS app ID must be 16 characters');
|
||||
}
|
||||
|
||||
if (!BOSS_APP_ID_FILTER_REGEX.test(bossAppID)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'BOSS app ID must only contain letters and numbers');
|
||||
}
|
||||
|
||||
if (country && !isValidCountryCode(country)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${country} is not a valid country`);
|
||||
}
|
||||
|
||||
if (language && !isValidLanguage(language)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${language} is not a valid language`);
|
||||
}
|
||||
|
||||
const files = await getTaskFiles(false, bossAppID, taskID, country, language);
|
||||
|
||||
return {
|
||||
files: files.map(file => ({
|
||||
deleted: file.deleted,
|
||||
dataId: file.data_id,
|
||||
taskId: file.task_id,
|
||||
bossAppId: file.boss_app_id,
|
||||
supportedCountries: file.supported_countries,
|
||||
supportedLanguages: file.supported_languages,
|
||||
attributes: {
|
||||
attribute1: file.attribute1,
|
||||
attribute2: file.attribute2,
|
||||
attribute3: file.attribute3,
|
||||
description: file.password
|
||||
},
|
||||
creatorPid: file.creator_pid,
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
hash: file.hash,
|
||||
size: file.size,
|
||||
notifyOnNew: file.notify_on_new,
|
||||
notifyLed: file.notify_led,
|
||||
conditionPlayed: 0n, // TODO - Don't stub this
|
||||
autoDelete: false, // TODO - Don't stub this
|
||||
createdTimestamp: file.created,
|
||||
updatedTimestamp: file.updated
|
||||
}))
|
||||
};
|
||||
}
|
||||
407
src/services/grpc/boss/v2/list-known-boss-apps.ts
Normal file
407
src/services/grpc/boss/v2/list-known-boss-apps.ts
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
import type { ListKnownBOSSAppsResponse } from '@pretendonetwork/grpc/boss/v2/list_known_boss_apps';
|
||||
|
||||
export async function listKnownBOSSApps(): Promise<ListKnownBOSSAppsResponse> {
|
||||
return {
|
||||
apps: [
|
||||
{
|
||||
bossAppId: 'WJDaV6ePVgrS0TRa',
|
||||
titleId: BigInt(0x0005003010016000),
|
||||
titleRegion: 'UNK',
|
||||
name: 'Unknown',
|
||||
tasks: ['olvinfo']
|
||||
},
|
||||
{
|
||||
bossAppId: 'VFoY6V7u7UUq1EG5',
|
||||
titleId: BigInt(0x0005003010016100),
|
||||
titleRegion: 'UNK',
|
||||
name: 'Unknown',
|
||||
tasks: ['olvinfo']
|
||||
},
|
||||
{
|
||||
bossAppId: '8MNOVprfNVAJjfCM',
|
||||
titleId: BigInt(0x0005003010016200),
|
||||
titleRegion: 'UNK',
|
||||
name: 'Unknown',
|
||||
tasks: ['olvinfo']
|
||||
},
|
||||
{
|
||||
bossAppId: 'v1cqzWykBKUg0rHQ',
|
||||
titleId: BigInt(0x000500301001900A),
|
||||
titleRegion: 'JPN',
|
||||
name: 'Miiverse Post All',
|
||||
tasks: ['solv']
|
||||
},
|
||||
{
|
||||
bossAppId: 'bieC9ACJlisFg5xS',
|
||||
titleId: BigInt(0x000500301001910A),
|
||||
titleRegion: 'USA',
|
||||
name: 'Miiverse Post All',
|
||||
tasks: ['solv']
|
||||
},
|
||||
{
|
||||
bossAppId: 'tOaQcoBLtPTgVN3Y',
|
||||
titleId: BigInt(0x000500301001920A),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Miiverse Post All',
|
||||
tasks: ['solv']
|
||||
},
|
||||
{
|
||||
bossAppId: 'HX8a16MMNn6i1z0Y',
|
||||
titleId: BigInt(0x000500301001400A),
|
||||
titleRegion: 'JPN',
|
||||
name: 'Nintendo eShop',
|
||||
tasks: ['wood1', 'woodBGM']
|
||||
},
|
||||
{
|
||||
bossAppId: '07E3nY6lAwlwrQRo',
|
||||
titleId: BigInt(0x000500301001410A),
|
||||
titleRegion: 'USA',
|
||||
name: 'Nintendo eShop',
|
||||
tasks: ['wood1', 'woodBGM']
|
||||
},
|
||||
{
|
||||
bossAppId: '8UsM86l8xgkjFk8z',
|
||||
titleId: BigInt(0x000500301001420A),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Nintendo eShop',
|
||||
tasks: ['wood1', 'woodBGM']
|
||||
},
|
||||
{
|
||||
bossAppId: 'IXmFUqR2qenXfF61',
|
||||
titleId: BigInt(0x0005001010066000),
|
||||
titleRegion: 'ALL',
|
||||
name: 'ECO Process',
|
||||
tasks: ['promo1', 'promo2', 'promo3', 'push']
|
||||
},
|
||||
{
|
||||
bossAppId: 'BMQAm5iUVtPsJVsU',
|
||||
titleId: BigInt(0x000500101004D000),
|
||||
titleRegion: 'JPN',
|
||||
name: 'Notifications',
|
||||
tasks: ['sysmsg1', 'sysmsg2']
|
||||
},
|
||||
{
|
||||
bossAppId: 'LRmanFo4Tx3kEGDp',
|
||||
titleId: BigInt(0x000500101004D100),
|
||||
titleRegion: 'USA',
|
||||
name: 'Notifications',
|
||||
tasks: ['sysmsg1', 'sysmsg2']
|
||||
},
|
||||
{
|
||||
bossAppId: 'TZr27FE8wzKiEaTO',
|
||||
titleId: BigInt(0x000500101004D200),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Notifications',
|
||||
tasks: ['sysmsg1', 'sysmsg2']
|
||||
},
|
||||
{
|
||||
bossAppId: 'JnIrm9c4E9JBmxBo',
|
||||
titleId: BigInt(0x0005000010185200),
|
||||
titleRegion: 'JPN',
|
||||
name: 'NewスーパーマリオブラザーズU 無料お試し版 (New SUPER MARIO BROS. U (Trial))',
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'dadlI27Ww8H2d56x',
|
||||
titleId: BigInt(0x0005000010101C00),
|
||||
titleRegion: 'JPN',
|
||||
name: 'NewスーパーマリオブラザーズU (New SUPER MARIO BROS. U)',
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'RaPn5saabzliYrpo',
|
||||
titleId: BigInt(0x0005000010101D00),
|
||||
titleRegion: 'USA',
|
||||
name: 'New SUPER MARIO BROS. U',
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: '14VFIK3rY2SP0WRE',
|
||||
titleId: BigInt(0x0005000010101E00),
|
||||
titleRegion: 'EUR',
|
||||
name: 'New SUPER MARIO BROS. U',
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'RbEQ44t2AocC4rvu',
|
||||
titleId: BigInt(0x000500001014B700),
|
||||
titleRegion: 'USA',
|
||||
name: 'New SUPER MARIO BROS. U + New SUPER LUIGI U',
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: '287gv3WZdxo1QRhl',
|
||||
titleId: BigInt(0x000500001014B800),
|
||||
titleRegion: 'EUR',
|
||||
name: 'New SUPER MARIO BROS. U + New SUPER LUIGI U',
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'bb6tOEckvgZ50ciH',
|
||||
titleId: BigInt(0x0005000010162B00),
|
||||
titleRegion: 'JPN',
|
||||
name: 'スプラトゥーン (Splatoon)',
|
||||
tasks: ['optdat2', 'schdat2', 'schdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'rjVlM7hUXPxmYQJh',
|
||||
titleId: BigInt(0x0005000010176900),
|
||||
titleRegion: 'USA',
|
||||
name: 'Splatoon',
|
||||
tasks: ['optdat2', 'schdat2', 'schdata', 'optdata2', 'schdata2']
|
||||
},
|
||||
{
|
||||
bossAppId: 'zvGSM4kOrXpkKnpT',
|
||||
titleId: BigInt(0x0005000010176A00),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Splatoon',
|
||||
tasks: ['optdat2', 'schdat2', 'schdata', 'optdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'm8KJPtmPweiPuETE',
|
||||
titleId: BigInt(0x000500001012F100),
|
||||
titleRegion: 'JPN',
|
||||
name: 'Wii Sports Club',
|
||||
tasks: ['sp1_ans']
|
||||
},
|
||||
{
|
||||
bossAppId: 'pO72Hi5uqf5yuNd8',
|
||||
titleId: BigInt(0x0005000010144D00),
|
||||
titleRegion: 'USA',
|
||||
name: 'Wii Sports Club',
|
||||
tasks: ['sp1_ans']
|
||||
},
|
||||
{
|
||||
bossAppId: '4m8Xme1wKgzwslTJ',
|
||||
titleId: BigInt(0x0005000010144E00),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Wii Sports Club',
|
||||
tasks: ['sp1_ans']
|
||||
},
|
||||
{
|
||||
bossAppId: 'ESLqtAhxS8KQU4eu',
|
||||
titleId: BigInt(0x000500001018DB00),
|
||||
titleRegion: 'JPN',
|
||||
name: 'Super Mario Maker (スーパーマリオメーカー)',
|
||||
tasks: ['CHARA']
|
||||
},
|
||||
{
|
||||
bossAppId: 'vGwChBW1ExOoHDsm',
|
||||
titleId: BigInt(0x000500001018DC00),
|
||||
titleRegion: 'USA',
|
||||
name: 'Super Mario Maker',
|
||||
tasks: ['CHARA']
|
||||
},
|
||||
{
|
||||
bossAppId: 'IeUc4hQsKKe9rJHB',
|
||||
titleId: BigInt(0x000500001018DD00),
|
||||
titleRegion: 'EUA',
|
||||
name: 'Super Mario Maker',
|
||||
tasks: ['CHARA']
|
||||
},
|
||||
{
|
||||
bossAppId: '4krJA4Gx3jF5nhQf',
|
||||
titleId: BigInt(0x000500001012BC00),
|
||||
titleRegion: 'JPN',
|
||||
name: 'ピクミン3 (PIKMIN 3)',
|
||||
tasks: ['histgrm']
|
||||
},
|
||||
{
|
||||
bossAppId: '9jRZEoWYLc3OG9a8',
|
||||
titleId: BigInt(0x000500001012BD00),
|
||||
titleRegion: 'USA',
|
||||
name: 'PIKMIN 3',
|
||||
tasks: ['histgrm']
|
||||
},
|
||||
{
|
||||
bossAppId: 'VWqUTspR5YtjDjxa',
|
||||
titleId: BigInt(0x000500001012BE00),
|
||||
titleRegion: 'EUR',
|
||||
name: 'PIKMIN 3',
|
||||
tasks: ['histgrm']
|
||||
},
|
||||
{
|
||||
bossAppId: 'Ge1KtMu8tYlf4AUM',
|
||||
titleId: BigInt(0x0005000010192000),
|
||||
titleRegion: 'JPN',
|
||||
name: '太鼓の達人 特盛り! (Taiko no Tatsujin Tokumori!)',
|
||||
tasks: ['notice1']
|
||||
},
|
||||
{
|
||||
bossAppId: 'gycVtTzCouZmukZ6',
|
||||
titleId: BigInt(0x0005000010110E00),
|
||||
titleRegion: 'JPN',
|
||||
name: '大乱闘スマッシュブラザーズ for Wii U (Super Smash Bros. for Wii U)',
|
||||
tasks: ['NEWS', 'amiibo']
|
||||
},
|
||||
{
|
||||
bossAppId: 'o2Ug1pIp9Uhri6Nh',
|
||||
titleId: BigInt(0x0005000010144F00),
|
||||
titleRegion: 'USA',
|
||||
name: 'Super Smash Bros. for Wii U',
|
||||
tasks: ['amiibo', 'NEWS', 'friend', 'CONQ']
|
||||
},
|
||||
{
|
||||
bossAppId: 'n6rAJ1nnfC1Sgcpl',
|
||||
titleId: BigInt(0x0005000010145000),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Super Smash Bros. for Wii U',
|
||||
tasks: ['amiibo', 'NEWS', 'friend', 'CONQ']
|
||||
},
|
||||
{
|
||||
bossAppId: 'CHUN6T1m7Xk4EBg4',
|
||||
titleId: BigInt(0x00050000101DFF00),
|
||||
titleRegion: 'JPN',
|
||||
name: 'プチコンBIG (Petitcom BIG)',
|
||||
tasks: ['ptcbnws']
|
||||
},
|
||||
{
|
||||
bossAppId: 'zyXdCW9jGdi9rjaz',
|
||||
titleId: BigInt(0x0005000010142200),
|
||||
titleRegion: 'JPN',
|
||||
name: 'NewスーパールイージU (New SUPER LUIGI U)',
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'jPHLlJr2fJyTzffp',
|
||||
titleId: BigInt(0x0005000010142300),
|
||||
titleRegion: 'USA',
|
||||
name: 'New SUPER LUIGI U',
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'YsXB6IRGSI56tPxl',
|
||||
titleId: BigInt(0x0005000010142400),
|
||||
titleRegion: 'EUR',
|
||||
name: 'New SUPER LUIGI U',
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'Lbqp9Sg1i0xUzFFa',
|
||||
titleId: BigInt(0x0005000010113800),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Zen Pinball 2',
|
||||
tasks: ['PTS']
|
||||
},
|
||||
{
|
||||
bossAppId: 'DwU7n0FidGrLNiOo',
|
||||
titleId: BigInt(0x000500001014D900),
|
||||
titleRegion: 'JPN',
|
||||
name: 'ぷよぷよテトリス (PUYOPUYOTETRIS)',
|
||||
tasks: ['boss1', 'boss2', 'boss3']
|
||||
},
|
||||
{
|
||||
bossAppId: 'yIUkFmuGVkGP8pDb',
|
||||
titleId: BigInt(0x0005000010132200),
|
||||
titleRegion: 'JPN',
|
||||
name: '太鼓の達人 Wii Uば~じょん! (Taiko no Tatsujin Wii U version!)',
|
||||
tasks: ['notice1']
|
||||
},
|
||||
{
|
||||
bossAppId: 'v4WRObSzD7VU3dcJ',
|
||||
titleId: BigInt(0x00050000101D3000),
|
||||
titleRegion: 'JPN',
|
||||
name: '太鼓の達人 あつめて★ともだち大作戦! (Taiko no Tatsujin Atsumete★ TomodachiDaisakusen!)',
|
||||
tasks: ['notice1']
|
||||
},
|
||||
{
|
||||
bossAppId: '3zDjXIA57bSceyaw',
|
||||
titleId: BigInt(0x00050000101BEC00),
|
||||
titleRegion: 'USA',
|
||||
name: 'Star Fox Guard',
|
||||
tasks: ['param']
|
||||
},
|
||||
{
|
||||
bossAppId: 'NL38jhExI2CQqhWd',
|
||||
titleId: BigInt(0x00050000101CDB00),
|
||||
titleRegion: 'JPN',
|
||||
name: 'Splatoon Pre-Launch Review',
|
||||
tasks: ['schdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'sE6KwEpQYyg6tdU7',
|
||||
titleId: BigInt(0x00050000101CDC00),
|
||||
titleRegion: 'USA',
|
||||
name: 'Splatoon Pre-Launch Review',
|
||||
tasks: ['schdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'pTKZ9q5KrCP3gBag',
|
||||
titleId: BigInt(0x00050000101CDD00),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Splatoon Pre-Launch Review',
|
||||
tasks: ['schdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'CJT88RO008LAnD51',
|
||||
titleId: BigInt(0x0005000010170600),
|
||||
titleRegion: 'JPN',
|
||||
name: '仮面ライダー バトライド・ウォーⅡ プレミアムTV&MOVIEサウンドED. (KAMEN RIDER BATTRIDE WAR Ⅱ PREMIUM TV&MOVIE SOUND ED.)',
|
||||
tasks: ['PE_GAK', 'PE_ZNG']
|
||||
},
|
||||
{
|
||||
bossAppId: 'FyyMFzEByuQJc6sJ',
|
||||
titleId: BigInt(0x0005000010135200),
|
||||
titleRegion: 'USA',
|
||||
name: 'Star Wars Pinball',
|
||||
tasks: ['PTS']
|
||||
},
|
||||
{
|
||||
bossAppId: 'A4yyXWKZZUToFtrt',
|
||||
titleId: BigInt(0x0005000010132A00),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Star Wars Pinball',
|
||||
tasks: ['PTS']
|
||||
},
|
||||
{
|
||||
bossAppId: 'HauaFQ1sPsnQ6rBj',
|
||||
titleId: BigInt(0x0005000010171F00),
|
||||
titleRegion: 'USA',
|
||||
name: 'Pushmo World',
|
||||
tasks: ['annouce']
|
||||
},
|
||||
{
|
||||
bossAppId: 'qDUeFmk0Az71nHyD',
|
||||
titleId: BigInt(0x0005000010110900),
|
||||
titleRegion: 'JPN',
|
||||
name: 'NINJA GAIDEN 3: Razor\'s Edge',
|
||||
tasks: ['DLCINFO']
|
||||
},
|
||||
{
|
||||
bossAppId: 'yVsSPM2E0DEOxroT',
|
||||
titleId: BigInt(0x0005000010110A00),
|
||||
titleRegion: 'USA',
|
||||
name: 'NINJA GAIDEN 3: Razor\'s Edge',
|
||||
tasks: ['DLCINFO']
|
||||
},
|
||||
{
|
||||
bossAppId: 'Xw6OvZkQofQ3O8Bi',
|
||||
titleId: BigInt(0x0005000010110B00),
|
||||
titleRegion: 'EUR',
|
||||
name: 'Ninja Gaiden 3: Razor\'s Edge',
|
||||
tasks: ['DLCINFO']
|
||||
},
|
||||
{
|
||||
bossAppId: 'LUQX5swEjBUPQ8nR',
|
||||
titleId: BigInt(0x0005000010110200),
|
||||
titleRegion: 'USA',
|
||||
name: 'WARRIORS OROCHI 3 Hyper(NA)',
|
||||
tasks: ['OR2H000']
|
||||
},
|
||||
{
|
||||
bossAppId: 'y4pXrgLe0JGao3No',
|
||||
titleId: BigInt(0x0005000010112B00),
|
||||
titleRegion: 'EUR',
|
||||
name: 'WARRIORS OROCHI 3 Hyper(EU)',
|
||||
tasks: ['OR2H000']
|
||||
},
|
||||
{
|
||||
bossAppId: 'j01mRJ9sNe00MWPC',
|
||||
titleId: BigInt(0x0005000010170700),
|
||||
titleRegion: 'JPN',
|
||||
name: '仮面ライダー バトライド・ウォーⅡ (KAMEN RIDER BATTRIDE WAR Ⅱ)',
|
||||
tasks: ['CHR_GAK', 'CHR_ZNG']
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
22
src/services/grpc/boss/v2/list-tasks.ts
Normal file
22
src/services/grpc/boss/v2/list-tasks.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { getAllTasks } from '@/database';
|
||||
import type { ListTasksResponse } from '@pretendonetwork/grpc/boss/v2/list_tasks';
|
||||
|
||||
export async function listTasks(): Promise<ListTasksResponse> {
|
||||
const tasks = await getAllTasks(false);
|
||||
|
||||
return {
|
||||
tasks: tasks.map(task => ({
|
||||
deleted: task.deleted,
|
||||
id: task.id,
|
||||
inGameId: task.in_game_id,
|
||||
bossAppId: task.boss_app_id,
|
||||
creatorPid: task.creator_pid,
|
||||
status: task.status,
|
||||
interval: 0, // TODO - Don't stub this
|
||||
titleId: BigInt(parseInt(task.title_id, 16)),
|
||||
description: task.description,
|
||||
createdTimestamp: task.created,
|
||||
updatedTimestamp: task.updated
|
||||
}))
|
||||
};
|
||||
}
|
||||
16
src/services/grpc/boss/v2/middleware/api-key-middleware.ts
Normal file
16
src/services/grpc/boss/v2/middleware/api-key-middleware.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { config } from '@/config-manager';
|
||||
import type { ServerMiddlewareCall, CallContext } from 'nice-grpc';
|
||||
|
||||
export async function* apiKeyMiddleware<Request, Response>(
|
||||
call: ServerMiddlewareCall<Request, Response>,
|
||||
context: CallContext
|
||||
): AsyncGenerator<Response, Response | void, undefined> {
|
||||
const apiKey: string | undefined = context.metadata.get('X-API-Key');
|
||||
|
||||
if (!apiKey || apiKey !== config.grpc.boss.api_key) {
|
||||
throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid API key');
|
||||
}
|
||||
|
||||
return yield* call.next(call.request, context);
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getUserDataByToken } from '@/util';
|
||||
import type { ServerMiddlewareCall, CallContext } from 'nice-grpc';
|
||||
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import type { PNIDPermissionFlags } from '@pretendonetwork/grpc/account/pnid_permission_flags';
|
||||
|
||||
export type AuthenticationCallContextExt = {
|
||||
user: GetUserDataResponse | null;
|
||||
};
|
||||
|
||||
export async function* authenticationMiddleware<Request, Response>(
|
||||
call: ServerMiddlewareCall<Request, Response, AuthenticationCallContextExt>,
|
||||
context: CallContext
|
||||
): AsyncGenerator<Response, Response | void, undefined> {
|
||||
const token: string | undefined = context.metadata.get('X-Token')?.trim();
|
||||
|
||||
try {
|
||||
let user: GetUserDataResponse | null = null;
|
||||
|
||||
if (token) {
|
||||
user = await getUserDataByToken(token);
|
||||
if (!user) {
|
||||
throw new ServerError(Status.UNAUTHENTICATED, 'User could not be found');
|
||||
}
|
||||
}
|
||||
|
||||
return yield* call.next(call.request, {
|
||||
...context,
|
||||
user
|
||||
});
|
||||
} catch (error) {
|
||||
let message: string = 'Unknown server error';
|
||||
|
||||
console.log(error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, message);
|
||||
}
|
||||
}
|
||||
|
||||
export function hasPermission(ctx: AuthenticationCallContextExt, perm: keyof PNIDPermissionFlags): boolean {
|
||||
if (!ctx.user) {
|
||||
return true; // Non users are always allowed
|
||||
}
|
||||
if (!ctx.user.permissions) {
|
||||
return false; // No permissions, no entry
|
||||
}
|
||||
return ctx.user.permissions[perm];
|
||||
}
|
||||
78
src/services/grpc/boss/v2/register-task.ts
Normal file
78
src/services/grpc/boss/v2/register-task.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { ServerError, Status } from 'nice-grpc';
|
||||
import { getTask } from '@/database';
|
||||
import { Task } from '@/models/task';
|
||||
import { hasPermission } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { RegisterTaskRequest, RegisterTaskResponse } from '@pretendonetwork/grpc/boss/v2/register_task';
|
||||
|
||||
const BOSS_APP_ID_FILTER_REGEX = /^[A-Za-z0-9]*$/;
|
||||
|
||||
export async function registerTask(request: RegisterTaskRequest, context: CallContext & AuthenticationCallContextExt): Promise<RegisterTaskResponse> {
|
||||
if (!hasPermission(context, 'createBossTasks')) {
|
||||
throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to register new tasks');
|
||||
}
|
||||
|
||||
const taskID = request.id.trim();
|
||||
const bossAppID = request.bossAppId.trim();
|
||||
const titleID = request.titleId.toString(16).toLowerCase().padStart(16, '0');
|
||||
const description = request.description.trim();
|
||||
|
||||
if (!taskID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing task ID');
|
||||
}
|
||||
|
||||
if (!bossAppID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing BOSS app ID');
|
||||
}
|
||||
|
||||
if (bossAppID.length !== 16) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'BOSS app ID must be 16 characters');
|
||||
}
|
||||
|
||||
if (!BOSS_APP_ID_FILTER_REGEX.test(bossAppID)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'BOSS app ID must only contain letters and numbers');
|
||||
}
|
||||
|
||||
if (await getTask(bossAppID, taskID)) {
|
||||
throw new ServerError(Status.ALREADY_EXISTS, `Task ${taskID} already exists for BOSS app ${bossAppID}`);
|
||||
}
|
||||
|
||||
// * BOSS tasks have 2 IDs
|
||||
// * - 1: The ID which is registered in-game
|
||||
// * - 2: The ID which is registered on the server
|
||||
// * The in-game task ID can be any length, but the
|
||||
// * ID registered on the server is capped at 7 characters.
|
||||
// * When querying tasks in the API, the server ignores
|
||||
// * all characters after the 7th. For example, Splatoon
|
||||
// * registers task optdata2 in-game, but the server
|
||||
// * tracks it as task optdata
|
||||
|
||||
const task = await Task.create({
|
||||
id: taskID.slice(0, 7),
|
||||
in_game_id: taskID,
|
||||
boss_app_id: bossAppID,
|
||||
creator_pid: context.user?.pid,
|
||||
status: 'open', // TODO - Make this configurable
|
||||
title_id: titleID,
|
||||
description: description,
|
||||
created: Date.now(),
|
||||
updated: Date.now()
|
||||
});
|
||||
|
||||
return {
|
||||
task: {
|
||||
deleted: task.deleted,
|
||||
id: task.id,
|
||||
inGameId: task.in_game_id,
|
||||
bossAppId: task.boss_app_id,
|
||||
creatorPid: task.creator_pid,
|
||||
status: task.status,
|
||||
interval: 0, // TODO - Don't stub this
|
||||
titleId: BigInt(parseInt(task.title_id, 16)),
|
||||
description: task.description,
|
||||
createdTimestamp: task.created,
|
||||
updatedTimestamp: task.updated
|
||||
}
|
||||
};
|
||||
}
|
||||
59
src/services/grpc/boss/v2/update-file-metadata-wup.ts
Normal file
59
src/services/grpc/boss/v2/update-file-metadata-wup.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getTaskFileByDataID } from '@/database';
|
||||
import { isValidFileNotifyCondition, isValidFileType } from '@/util';
|
||||
import { hasPermission } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { UpdateFileMetadataWUPRequest } from '@pretendonetwork/grpc/boss/v2/update_file_metadata_wup';
|
||||
import type { Empty } from '@pretendonetwork/grpc/google/protobuf/empty';
|
||||
|
||||
export async function updateFileMetadataWUP(request: UpdateFileMetadataWUPRequest, context: CallContext & AuthenticationCallContextExt): Promise<Empty> {
|
||||
if (!hasPermission(context, 'updateBossFiles')) {
|
||||
throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to update file metadata');
|
||||
}
|
||||
|
||||
const dataID = request.dataId;
|
||||
const updateData = request.updateData;
|
||||
|
||||
if (!dataID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing file data ID');
|
||||
}
|
||||
|
||||
if (!updateData) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing file update data');
|
||||
}
|
||||
|
||||
const file = await getTaskFileByDataID(dataID);
|
||||
|
||||
if (!file || file.deleted) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `File ${dataID} not found`);
|
||||
}
|
||||
|
||||
if (!isValidFileType(updateData.type)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${updateData.type} is not a valid type`);
|
||||
}
|
||||
|
||||
for (const notifyCondition of updateData.notifyOnNew) {
|
||||
if (!isValidFileNotifyCondition(notifyCondition)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${notifyCondition} is not a valid notify condition`);
|
||||
}
|
||||
}
|
||||
|
||||
file.task_id = updateData.taskId.slice(0, 7);
|
||||
file.boss_app_id = updateData.bossAppId;
|
||||
file.supported_countries = updateData.supportedCountries;
|
||||
file.supported_languages = updateData.supportedLanguages;
|
||||
file.password = updateData.attributes ? updateData.attributes.description : file.password;
|
||||
file.attribute1 = updateData.attributes ? updateData.attributes.attribute1 : file.attribute1;
|
||||
file.attribute2 = updateData.attributes ? updateData.attributes.attribute2 : file.attribute2;
|
||||
file.attribute3 = updateData.attributes ? updateData.attributes.attribute3 : file.attribute3;
|
||||
file.name = updateData.name;
|
||||
file.type = updateData.type;
|
||||
file.notify_on_new = updateData.notifyOnNew;
|
||||
file.notify_led = updateData.notifyLed;
|
||||
file.updated = BigInt(Date.now());
|
||||
|
||||
await file.save();
|
||||
|
||||
return {};
|
||||
}
|
||||
54
src/services/grpc/boss/v2/update-task.ts
Normal file
54
src/services/grpc/boss/v2/update-task.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getTask } from '@/database';
|
||||
import { hasPermission } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { UpdateTaskRequest } from '@pretendonetwork/grpc/boss/v2/update_task';
|
||||
import type { Empty } from '@pretendonetwork/grpc/google/protobuf/empty';
|
||||
|
||||
export async function updateTask(request: UpdateTaskRequest, context: CallContext & AuthenticationCallContextExt): Promise<Empty> {
|
||||
if (!hasPermission(context, 'updateBossTasks')) {
|
||||
throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to update tasks');
|
||||
}
|
||||
|
||||
const taskID = request.id.trim();
|
||||
const bossAppID = request.bossAppId.trim();
|
||||
const updateData = request.updateData;
|
||||
|
||||
if (!taskID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing task ID');
|
||||
}
|
||||
|
||||
if (!bossAppID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing BOSS app ID');
|
||||
}
|
||||
|
||||
if (!updateData) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing task update data');
|
||||
}
|
||||
|
||||
const task = await getTask(bossAppID, taskID);
|
||||
|
||||
if (!task) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `Task ${taskID} not found for BOSS app ${bossAppID}`);
|
||||
}
|
||||
|
||||
if (updateData.status !== 'open') {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `Status ${updateData.status} is invalid`);
|
||||
}
|
||||
|
||||
if (updateData.id) {
|
||||
task.id = updateData.id.slice(0, 7);
|
||||
task.in_game_id = updateData.id;
|
||||
}
|
||||
|
||||
task.boss_app_id = updateData.bossAppId ? updateData.bossAppId : task.boss_app_id;
|
||||
task.title_id = updateData.titleId ? updateData.titleId.toString(16).toLowerCase().padStart(16, '0') : task.title_id;
|
||||
task.status = updateData.status ? updateData.status : task.status;
|
||||
task.description = updateData.description ? updateData.description : task.description;
|
||||
task.updated = BigInt(Date.now());
|
||||
|
||||
await task.save();
|
||||
|
||||
return {};
|
||||
}
|
||||
174
src/services/grpc/boss/v2/upload-file-wup.ts
Normal file
174
src/services/grpc/boss/v2/upload-file-wup.ts
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { encryptWiiU } from '@pretendonetwork/boss-crypto';
|
||||
import { isValidCountryCode, isValidFileNotifyCondition, isValidFileType, isValidLanguage, md5 } from '@/util';
|
||||
import { getTask, getTaskFile } from '@/database';
|
||||
import { File } from '@/models/file';
|
||||
import { config } from '@/config-manager';
|
||||
import { uploadCDNFile } from '@/cdn';
|
||||
import { hasPermission } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { UploadFileWUPRequest, UploadFileWUPResponse } from '@pretendonetwork/grpc/boss/v2/upload_file_wup';
|
||||
|
||||
const BOSS_APP_ID_FILTER_REGEX = /^[A-Za-z0-9]*$/;
|
||||
|
||||
export async function uploadFileWUP(request: UploadFileWUPRequest, context: CallContext & AuthenticationCallContextExt): Promise<UploadFileWUPResponse> {
|
||||
if (!hasPermission(context, 'uploadBossFiles')) {
|
||||
throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to upload new files');
|
||||
}
|
||||
|
||||
const taskID = request.taskId.trim();
|
||||
const bossAppID = request.bossAppId.trim();
|
||||
const supportedCountries = request.supportedCountries;
|
||||
const supportedLanguages = request.supportedLanguages;
|
||||
const name = request.name.trim();
|
||||
const type = request.type.trim();
|
||||
const notifyOnNew = [...new Set(request.notifyOnNew)];
|
||||
const notifyLed = request.notifyLed;
|
||||
const data = request.data;
|
||||
const nameEqualsDataID = request.nameEqualsDataId;
|
||||
|
||||
if (!taskID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing task ID');
|
||||
}
|
||||
|
||||
if (!bossAppID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Missing BOSS app ID');
|
||||
}
|
||||
|
||||
if (bossAppID.length !== 16) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'BOSS app ID must be 16 characters');
|
||||
}
|
||||
|
||||
if (!BOSS_APP_ID_FILTER_REGEX.test(bossAppID)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'BOSS app ID must only contain letters and numbers');
|
||||
}
|
||||
|
||||
if (!(await getTask(bossAppID, taskID))) {
|
||||
throw new ServerError(Status.NOT_FOUND, `Task ${taskID} does not exist for BOSS app ${bossAppID}`);
|
||||
}
|
||||
|
||||
for (const country of supportedCountries) {
|
||||
if (!isValidCountryCode(country)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${country} is not a valid country`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const language of supportedLanguages) {
|
||||
if (!isValidLanguage(language)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${language} is not a valid language`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!name && !nameEqualsDataID) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Must provide a file name is enable nameEqualsDataId');
|
||||
}
|
||||
|
||||
if (!isValidFileType(type)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${type} is not a valid type`);
|
||||
}
|
||||
|
||||
for (const notifyCondition of notifyOnNew) {
|
||||
if (!isValidFileNotifyCondition(notifyCondition)) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, `${notifyCondition} is not a valid notify condition`);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Cannot upload empty file');
|
||||
}
|
||||
|
||||
let encryptedData: Buffer;
|
||||
|
||||
try {
|
||||
encryptedData = encryptWiiU(data, config.crypto.wup.aes_key, config.crypto.wup.hmac_key);
|
||||
} catch (error: unknown) {
|
||||
let message = 'Unknown file encryption error';
|
||||
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
throw new ServerError(Status.ABORTED, message);
|
||||
}
|
||||
|
||||
const contentHash = md5(encryptedData);
|
||||
|
||||
// * Upload file first to prevent ghost DB entries on upload failures
|
||||
const key = `${bossAppID}/${taskID}/${contentHash}`;
|
||||
try {
|
||||
// * Some tasks have file names which are dynamic.
|
||||
// * They change depending on the files data ID.
|
||||
// * Because of this, using the file name in the
|
||||
// * upload key is not viable, as it is not always
|
||||
// * known during upload
|
||||
await uploadCDNFile('taskFile', key, encryptedData);
|
||||
} catch (error: unknown) {
|
||||
let message = 'Unknown file upload error';
|
||||
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
throw new ServerError(Status.ABORTED, message);
|
||||
}
|
||||
|
||||
let file = await getTaskFile(bossAppID, taskID, name);
|
||||
|
||||
if (file) {
|
||||
file.deleted = true;
|
||||
file.updated = BigInt(Date.now());
|
||||
|
||||
await file.save();
|
||||
}
|
||||
|
||||
file = await File.create({
|
||||
task_id: taskID.slice(0, 7),
|
||||
boss_app_id: bossAppID,
|
||||
file_key: key,
|
||||
supported_countries: supportedCountries,
|
||||
supported_languages: supportedLanguages,
|
||||
creator_pid: context.user?.pid,
|
||||
name: name,
|
||||
type: type,
|
||||
hash: contentHash,
|
||||
size: BigInt(encryptedData.length),
|
||||
notify_on_new: notifyOnNew,
|
||||
notify_led: notifyLed,
|
||||
created: Date.now(),
|
||||
updated: Date.now()
|
||||
});
|
||||
|
||||
if (nameEqualsDataID) {
|
||||
file.name = file.data_id.toString(16).padStart(8, '0');
|
||||
await file.save();
|
||||
}
|
||||
|
||||
return {
|
||||
file: {
|
||||
deleted: file.deleted,
|
||||
dataId: file.data_id,
|
||||
taskId: file.task_id,
|
||||
bossAppId: file.boss_app_id,
|
||||
supportedCountries: file.supported_countries,
|
||||
supportedLanguages: file.supported_languages,
|
||||
attributes: {
|
||||
attribute1: file.attribute1,
|
||||
attribute2: file.attribute2,
|
||||
attribute3: file.attribute3,
|
||||
description: file.password
|
||||
},
|
||||
creatorPid: file.creator_pid,
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
hash: file.hash,
|
||||
size: file.size,
|
||||
notifyOnNew: file.notify_on_new,
|
||||
notifyLed: file.notify_led,
|
||||
conditionPlayed: 0n, // TODO - Don't stub this
|
||||
autoDelete: false, // TODO - Don't stub this
|
||||
createdTimestamp: file.created,
|
||||
updatedTimestamp: file.updated
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -3,6 +3,12 @@ import { BOSSDefinition as BossServiceDefinitionV1 } from '@pretendonetwork/grpc
|
|||
import { apiKeyMiddleware as apiKeyMiddlewareV1 } from '@/services/grpc/boss/v1/middleware/api-key-middleware';
|
||||
import { authenticationMiddleware as authenticationMiddlewareV1 } from '@/services/grpc/boss/v1/middleware/authentication-middleware';
|
||||
import { bossServiceImplementationV1 } from '@/services/grpc/boss/v1/implementation';
|
||||
|
||||
import { BossServiceDefinition as BossServiceDefinitionV2 } from '@pretendonetwork/grpc/boss/v2/boss_service';
|
||||
import { apiKeyMiddleware as apiKeyMiddlewareV2 } from '@/services/grpc/boss/v2/middleware/api-key-middleware';
|
||||
import { authenticationMiddleware as authenticationMiddlewareV2 } from '@/services/grpc/boss/v2/middleware/authentication-middleware';
|
||||
import { bossServiceImplementationV2 } from '@/services/grpc/boss/v2/implementation';
|
||||
|
||||
import { config } from '@/config-manager';
|
||||
import type { Server } from 'nice-grpc';
|
||||
|
||||
|
|
@ -10,6 +16,7 @@ export async function startGRPCServer(): Promise<void> {
|
|||
const server: Server = createServer();
|
||||
|
||||
server.with(apiKeyMiddlewareV1).with(authenticationMiddlewareV1).add(BossServiceDefinitionV1, bossServiceImplementationV1);
|
||||
server.with(apiKeyMiddlewareV2).with(authenticationMiddlewareV2).add(BossServiceDefinitionV2, bossServiceImplementationV2);
|
||||
|
||||
await server.listen(`${config.grpc.boss.address}:${config.grpc.boss.port}`);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user