mirror of
https://github.com/PretendoNetwork/BOSS.git
synced 2026-07-12 23:02:09 -05:00
Merge pull request #15 from PretendoNetwork/memory-fixes
This commit is contained in:
commit
644081e615
11
.editorconfig
Normal file
11
.editorconfig
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[*]
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
dist
|
||||
*.js
|
||||
23
.github/workflows/lint.yml
vendored
Normal file
23
.github/workflows/lint.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request: {}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
113
eslint.config.mjs
Normal file
113
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// @ts-check
|
||||
|
||||
/* eslint-disable import/no-named-as-default-member -- We want to be able to use the full package name for the imports here for clarity */
|
||||
|
||||
import eslint from '@eslint/js';
|
||||
import eslintCommentPlugin from '@eslint-community/eslint-plugin-eslint-comments/configs';
|
||||
import stylisticPlugin from '@stylistic/eslint-plugin';
|
||||
// @ts-expect-error importPlugin is not typed
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
/**
|
||||
* Typed to any as the type is incompatible for some reason - it works!
|
||||
* @type {any}
|
||||
*/
|
||||
const stylisticConfig = stylisticPlugin.configs.customize({
|
||||
indent: 'tab',
|
||||
quotes: 'single',
|
||||
semi: true,
|
||||
commaDangle: 'never',
|
||||
braceStyle: '1tbs'
|
||||
});
|
||||
|
||||
/**
|
||||
* Typed to any as the type is incompatible for some reason - it works!
|
||||
* @type {any}
|
||||
*/
|
||||
const eslintCommentPluginConfig = eslintCommentPlugin.recommended;
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
// https://eslint.org/docs/rules/
|
||||
extends: [eslint.configs.recommended],
|
||||
rules: {
|
||||
'require-atomic-updates': 'off', // This rule is widely controversial and causes false positives
|
||||
'no-console': 'off',
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
'no-unused-vars': 'off',
|
||||
'one-var': ['error', 'never']
|
||||
}
|
||||
},
|
||||
{
|
||||
// https://typescript-eslint.io/rules/
|
||||
extends: [tseslint.configs.recommended],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ argsIgnorePattern: '^_' }
|
||||
],
|
||||
'@typescript-eslint/no-inferrable-types': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': ['off'],
|
||||
'@typescript-eslint/no-import-type-side-effects': 'error',
|
||||
'@typescript-eslint/consistent-type-imports': ['error', {
|
||||
fixStyle: 'separate-type-imports'
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
// https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/
|
||||
extends: [eslintCommentPluginConfig],
|
||||
rules: {
|
||||
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
|
||||
'@eslint-community/eslint-comments/require-description': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
// https://eslint.style/rules
|
||||
extends: [stylisticConfig],
|
||||
rules: {
|
||||
'@stylistic/no-extra-semi': 'error',
|
||||
'@stylistic/yield-star-spacing': ['error', 'after'],
|
||||
'@stylistic/operator-linebreak': ['error', 'after', { overrides: { '?': 'before', ':': 'before' } }],
|
||||
'@stylistic/curly-newline': ['error', {
|
||||
multiline: true,
|
||||
consistent: true
|
||||
}],
|
||||
'@stylistic/object-curly-newline': ['error', {
|
||||
multiline: true,
|
||||
consistent: true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
// https://www.npmjs.com/package/eslint-plugin-import
|
||||
extends: [importPlugin.flatConfigs.recommended, importPlugin.flatConfigs.warnings, importPlugin.flatConfigs.typescript],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
project: './tsconfig.json'
|
||||
},
|
||||
node: true
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'import/order': ['warn', {
|
||||
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
'newlines-between': 'never'
|
||||
}],
|
||||
'import/first': 'error',
|
||||
'import/consistent-type-specifier-style': ['error', 'prefer-top-level']
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'scripts/*',
|
||||
'dist/*'
|
||||
]
|
||||
}
|
||||
);
|
||||
52
logger.js
52
logger.js
|
|
@ -1,52 +0,0 @@
|
|||
const fs = require('fs-extra');
|
||||
require('colors');
|
||||
|
||||
const root = __dirname;
|
||||
fs.ensureDirSync(`${root}/logs`);
|
||||
|
||||
const streams = {
|
||||
latest: fs.createWriteStream(`${root}/logs/latest.log`),
|
||||
success: fs.createWriteStream(`${root}/logs/success.log`),
|
||||
error: fs.createWriteStream(`${root}/logs/error.log`),
|
||||
warn: fs.createWriteStream(`${root}/logs/warn.log`),
|
||||
info: fs.createWriteStream(`${root}/logs/info.log`)
|
||||
};
|
||||
|
||||
function success(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`;
|
||||
streams.success.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.green.bold);
|
||||
}
|
||||
|
||||
function error(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`;
|
||||
streams.error.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.red.bold);
|
||||
}
|
||||
|
||||
function warn(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`;
|
||||
streams.warn.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.yellow.bold);
|
||||
}
|
||||
|
||||
function info(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`;
|
||||
streams.info.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.cyan.bold);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
success,
|
||||
error,
|
||||
warn,
|
||||
info
|
||||
};
|
||||
9784
package-lock.json
generated
9784
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
|
|
@ -5,6 +5,7 @@
|
|||
"main": "dist/server.js",
|
||||
"scripts": {
|
||||
"lint": "npx eslint .",
|
||||
"lint:fix": "npx eslint . --fix",
|
||||
"build": "npm run lint && npm run clean && npx tsc && npx tsc-alias",
|
||||
"clean": "rimraf ./dist",
|
||||
"start": "node --enable-source-maps ."
|
||||
|
|
@ -13,36 +14,41 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.395.0",
|
||||
"@aws-sdk/client-s3": "^3.723.0",
|
||||
"@pretendonetwork/boss-crypto": "^1.0.0",
|
||||
"@pretendonetwork/grpc": "^1.0.6",
|
||||
"@typegoose/auto-increment": "^3.6.1",
|
||||
"boss-js": "github:PretendoNetwork/boss-js",
|
||||
"cacache": "^18.0.0",
|
||||
"cacache": "^19.0.1",
|
||||
"colors": "^1.4.0",
|
||||
"dicer": "^0.3.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^4.17.1",
|
||||
"express-subdomain": "^1.0.5",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"express-subdomain": "^1.0.6",
|
||||
"fs-extra": "^11.2.0",
|
||||
"moment": "^2.29.4",
|
||||
"mongoose": "^7.4.3",
|
||||
"moment": "^2.30.1",
|
||||
"mongoose": "~7.6.1",
|
||||
"morgan": "^1.10.0",
|
||||
"nice-grpc": "^2.1.5",
|
||||
"nice-grpc": "^2.1.10",
|
||||
"xmlbuilder": "^15.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
|
||||
"@smithy/types": "^4.0.0",
|
||||
"@stylistic/eslint-plugin": "^2.12.1",
|
||||
"@types/dicer": "^0.2.4",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/morgan": "^1.9.4",
|
||||
"@types/node": "^20.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
||||
"@typescript-eslint/parser": "^6.4.0",
|
||||
"axios": "^1.6.2",
|
||||
"eslint": "^8.47.0",
|
||||
"tsc-alias": "^1.8.7",
|
||||
"typescript": "^5.1.6",
|
||||
"xmlbuilder2": "^3.0.2"
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@types/node": "^22.10.5",
|
||||
"@typescript-eslint/parser": "^8.19.1",
|
||||
"axios": "^1.7.9",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-import-resolver-typescript": "^3.7.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"tsc-alias": "^1.8.10",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.19.1",
|
||||
"xmlbuilder2": "^3.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import crypto from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import fs from 'fs-extra';
|
||||
import mongoose from 'mongoose';
|
||||
import dotenv from 'dotenv';
|
||||
import { LOG_INFO, LOG_WARN, LOG_ERROR } from '@/logger';
|
||||
import { DisabledFeatures, Config } from '@/types/common/config';
|
||||
import type mongoose from 'mongoose';
|
||||
import type { DisabledFeatures, Config } from '@/types/common/config';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ export const config: Config = {
|
|||
secret: process.env.PN_BOSS_CONFIG_S3_ACCESS_SECRET?.trim() || ''
|
||||
},
|
||||
disk_path: process.env.PN_BOSS_CONFIG_CDN_DISK_PATH?.trim() || ''
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
LOG_INFO('Config loaded, checking integrity');
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import { CECSlot } from '@/models/cec-slot';
|
|||
import { Task } from '@/models/task';
|
||||
import { File } from '@/models/file';
|
||||
import { config } from '@/config-manager';
|
||||
import { HydratedCECDataDocument } from '@/types/mongoose/cec-data';
|
||||
import { HydratedCECSlotDocument, ICECSlot } from '@/types/mongoose/cec-slot';
|
||||
import { HydratedTaskDocument, ITask } from '@/types/mongoose/task';
|
||||
import { HydratedFileDocument, IFile } from '@/types/mongoose/file';
|
||||
import type { HydratedCECDataDocument } from '@/types/mongoose/cec-data';
|
||||
import type { HydratedCECSlotDocument, ICECSlot } from '@/types/mongoose/cec-slot';
|
||||
import type { HydratedTaskDocument, ITask } from '@/types/mongoose/task';
|
||||
import type { HydratedFileDocument, IFile } from '@/types/mongoose/file';
|
||||
|
||||
const connection_string: string = config.mongoose.connection_string;
|
||||
const options: mongoose.ConnectOptions = config.mongoose.options;
|
||||
|
|
@ -168,7 +168,7 @@ export async function getRandomCECData(pids: number[], gameID: number): Promise<
|
|||
// * We search through the CECSlot so that everyone has the same chance of getting their data picked up
|
||||
const filter: mongoose.FilterQuery<ICECSlot> = {
|
||||
creator_pid: {
|
||||
$in: pids,
|
||||
$in: pids
|
||||
},
|
||||
game_id: gameID
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,34 +14,42 @@ const streams = {
|
|||
info: fs.createWriteStream(`${root}/logs/info.log`)
|
||||
} as const;
|
||||
|
||||
function getCurrentTimestamp(): string {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0'); // * Months are 0-indexed
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
export function LOG_SUCCESS(input: string): void {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`;
|
||||
input = `[${getCurrentTimestamp()}] [SUCCESS]: ${input}`;
|
||||
streams.success.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.green.bold);
|
||||
}
|
||||
|
||||
export function LOG_ERROR(input: string): void {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`;
|
||||
input = `[${getCurrentTimestamp()}] [ERROR]: ${input}`;
|
||||
streams.error.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.red.bold);
|
||||
console.error(`${input}`.red.bold);
|
||||
}
|
||||
|
||||
export function LOG_WARN(input: string): void {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`;
|
||||
input = `[${getCurrentTimestamp()}] [WARN]: ${input}`;
|
||||
streams.warn.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.yellow.bold);
|
||||
}
|
||||
|
||||
export function LOG_INFO(input: string): void {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`;
|
||||
input = `[${getCurrentTimestamp()}] [INFO]: ${input}`;
|
||||
streams.info.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.cyan.bold);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import express from 'express';
|
||||
import { getNEXDataByPID } from '@/util';
|
||||
import type express from 'express';
|
||||
|
||||
export default async function authenticationMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise<void> {
|
||||
if (request.pid) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import express from 'express';
|
||||
import { CTRSystemModel } from '@/types/common/user-agent-info';
|
||||
import type { UserAgentInfo } from '@/types/common/user-agent-info';
|
||||
import type express from 'express';
|
||||
// import RequestException from '@/request-exception';
|
||||
import { CTRSystemModel, UserAgentInfo } from '@/types/common/user-agent-info';
|
||||
|
||||
const FIRMWARE_PATCH_REGION_WIIU_REGEX = /(\d)([JEU])/;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { ICECData, ICECDataMethods, CECDataModel } from '@/types/mongoose/cec-data';
|
||||
import type { ICECData, ICECDataMethods, CECDataModel } from '@/types/mongoose/cec-data';
|
||||
|
||||
const CECDataSchema = new mongoose.Schema<ICECData, CECDataModel, ICECDataMethods>({
|
||||
creator_pid: Number,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { ICECSlot, ICECSlotMethods, CECSlotModel } from '@/types/mongoose/cec-slot';
|
||||
import type { ICECSlot, ICECSlotMethods, CECSlotModel } from '@/types/mongoose/cec-slot';
|
||||
|
||||
const CECSlotSchema = new mongoose.Schema<ICECSlot, CECSlotModel, ICECSlotMethods>({
|
||||
creator_pid: Number,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { AutoIncrementID } from '@typegoose/auto-increment';
|
||||
import { IFile, IFileMethods, FileModel } from '@/types/mongoose/file';
|
||||
import { AutoIncrementID } from '@typegoose/auto-increment';
|
||||
import type { IFile, IFileMethods, FileModel } from '@/types/mongoose/file';
|
||||
|
||||
const FileSchema = new mongoose.Schema<IFile, FileModel, IFileMethods>({
|
||||
deleted: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { ITask, ITaskMethods, TaskModel } from '@/types/mongoose/task';
|
||||
import type { ITask, ITaskMethods, TaskModel } from '@/types/mongoose/task';
|
||||
|
||||
const TaskSchema = new mongoose.Schema<ITask, TaskModel, ITaskMethods>({
|
||||
deleted: {
|
||||
|
|
@ -12,7 +12,7 @@ const TaskSchema = new mongoose.Schema<ITask, TaskModel, ITaskMethods>({
|
|||
creator_pid: Number,
|
||||
status: {
|
||||
type: String,
|
||||
enum : ['open'] // TODO - What else is there?
|
||||
enum: ['open'] // TODO - What else is there?
|
||||
},
|
||||
title_id: String,
|
||||
description: Number,
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ export default class RequestException extends Error {
|
|||
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
process.title = 'Pretendo - BOSS';
|
||||
process.on('SIGTERM', () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
import express from 'express';
|
||||
import morgan from 'morgan';
|
||||
import { connect as connectDatabase } from '@/database';
|
||||
|
|
@ -10,10 +5,8 @@ import { startGRPCServer } from '@/services/grpc/server';
|
|||
import RequestException from '@/request-exception';
|
||||
import { LOG_INFO, LOG_SUCCESS } from '@/logger';
|
||||
import { config } from '@/config-manager';
|
||||
|
||||
import parseUserAgentMiddleware from '@/middleware/parse-user-agent';
|
||||
import authenticationMiddleware from '@/middleware/authentication';
|
||||
|
||||
import nppl from '@/services/nppl';
|
||||
import npts from '@/services/npts';
|
||||
import npdi from '@/services/npdi';
|
||||
|
|
@ -21,6 +14,11 @@ import npfl from '@/services/npfl';
|
|||
import npdl from '@/services/npdl';
|
||||
import spr from '@/services/spr';
|
||||
|
||||
process.title = 'Pretendo - BOSS';
|
||||
process.on('SIGTERM', () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
const app = express();
|
||||
|
||||
LOG_INFO('Setting up Middleware');
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { CallContext, Status, ServerError } from 'nice-grpc';
|
||||
import { DeleteFileRequest } from '@pretendonetwork/grpc/boss/delete_file';
|
||||
import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getTaskFileByDataID } from '@/database';
|
||||
import { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { DeleteFileRequest } from '@pretendonetwork/grpc/boss/delete_file';
|
||||
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import type { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
|
||||
export async function deleteFile(request: DeleteFileRequest, context: CallContext & AuthenticationCallContextExt): Promise<Empty> {
|
||||
// * This is asserted in authentication middleware, we know this is never null
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { CallContext, Status, ServerError } from 'nice-grpc';
|
||||
import { DeleteTaskRequest } from '@pretendonetwork/grpc/boss/delete_task';
|
||||
import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getTask } from '@/database';
|
||||
import { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { DeleteTaskRequest } from '@pretendonetwork/grpc/boss/delete_task';
|
||||
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import type { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty';
|
||||
|
||||
export async function deleteTask(request: DeleteTaskRequest, context: CallContext & AuthenticationCallContextExt): Promise<Empty> {
|
||||
// * This is asserted in authentication middleware, we know this is never null
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { BOSSServiceImplementation } from '@pretendonetwork/grpc/boss/boss_service';
|
||||
import { listKnownBOSSApps } from '@/services/grpc/boss/list-known-boss-apps';
|
||||
import { listTasks } from '@/services/grpc/boss/list-tasks';
|
||||
import { registerTask } from '@/services/grpc/boss/register-task';
|
||||
|
|
@ -8,6 +7,7 @@ import { listFiles } from '@/services/grpc/boss/list-files';
|
|||
import { uploadFile } from '@/services/grpc/boss/upload-file';
|
||||
import { updateFileMetadata } from '@/services/grpc/boss/update-file-metadata';
|
||||
import { deleteFile } from '@/services/grpc/boss/delete-file';
|
||||
import type { BOSSServiceImplementation } from '@pretendonetwork/grpc/boss/boss_service';
|
||||
|
||||
export const implementation: BOSSServiceImplementation = {
|
||||
listKnownBOSSApps,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { ListFilesRequest, ListFilesResponse } from '@pretendonetwork/grpc/boss/list_files';
|
||||
import { isValidCountryCode, isValidLanguage } from '@/util';
|
||||
import { getTaskFiles } from '@/database';
|
||||
import type { ListFilesRequest, ListFilesResponse } from '@pretendonetwork/grpc/boss/list_files';
|
||||
|
||||
const BOSS_APP_ID_FILTER_REGEX = /^[A-Za-z0-9]*$/;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ListKnownBOSSAppsResponse } from '@pretendonetwork/grpc/boss/list_known_boss_apps';
|
||||
import type { ListKnownBOSSAppsResponse } from '@pretendonetwork/grpc/boss/list_known_boss_apps';
|
||||
|
||||
export async function listKnownBOSSApps(): Promise<ListKnownBOSSAppsResponse> {
|
||||
return {
|
||||
|
|
@ -8,400 +8,400 @@ export async function listKnownBOSSApps(): Promise<ListKnownBOSSAppsResponse> {
|
|||
titleId: '0005003010016000',
|
||||
titleRegion: 'UNK',
|
||||
name: 'Unknown',
|
||||
tasks: [ 'olvinfo' ]
|
||||
tasks: ['olvinfo']
|
||||
},
|
||||
{
|
||||
bossAppId: 'VFoY6V7u7UUq1EG5',
|
||||
titleId: '0005003010016100',
|
||||
titleRegion: 'UNK',
|
||||
name: 'Unknown',
|
||||
tasks: [ 'olvinfo' ]
|
||||
tasks: ['olvinfo']
|
||||
},
|
||||
{
|
||||
bossAppId: '8MNOVprfNVAJjfCM',
|
||||
titleId: '0005003010016200',
|
||||
titleRegion: 'UNK',
|
||||
name: 'Unknown',
|
||||
tasks: [ 'olvinfo' ]
|
||||
tasks: ['olvinfo']
|
||||
},
|
||||
{
|
||||
bossAppId: 'v1cqzWykBKUg0rHQ',
|
||||
titleId: '000500301001900A',
|
||||
titleRegion: 'JPN',
|
||||
name: 'Miiverse Post All',
|
||||
tasks: [ 'solv' ]
|
||||
tasks: ['solv']
|
||||
},
|
||||
{
|
||||
bossAppId: 'bieC9ACJlisFg5xS',
|
||||
titleId: '000500301001910A',
|
||||
titleRegion: 'USA',
|
||||
name: 'Miiverse Post All',
|
||||
tasks: [ 'solv' ]
|
||||
tasks: ['solv']
|
||||
},
|
||||
{
|
||||
bossAppId: 'tOaQcoBLtPTgVN3Y',
|
||||
titleId: '000500301001920A',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Miiverse Post All',
|
||||
tasks: [ 'solv' ]
|
||||
tasks: ['solv']
|
||||
},
|
||||
{
|
||||
bossAppId: 'HX8a16MMNn6i1z0Y',
|
||||
titleId: '000500301001400A',
|
||||
titleRegion: 'JPN',
|
||||
name: 'Nintendo eShop',
|
||||
tasks: [ 'wood1', 'woodBGM' ]
|
||||
tasks: ['wood1', 'woodBGM']
|
||||
},
|
||||
{
|
||||
bossAppId: '07E3nY6lAwlwrQRo',
|
||||
titleId: '000500301001410A',
|
||||
titleRegion: 'USA',
|
||||
name: 'Nintendo eShop',
|
||||
tasks: [ 'wood1', 'woodBGM' ]
|
||||
tasks: ['wood1', 'woodBGM']
|
||||
},
|
||||
{
|
||||
bossAppId: '8UsM86l8xgkjFk8z',
|
||||
titleId: '000500301001420A',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Nintendo eShop',
|
||||
tasks: [ 'wood1', 'woodBGM' ]
|
||||
tasks: ['wood1', 'woodBGM']
|
||||
},
|
||||
{
|
||||
bossAppId: 'IXmFUqR2qenXfF61',
|
||||
titleId: '0005001010066000',
|
||||
titleRegion: 'ALL',
|
||||
name: 'ECO Process',
|
||||
tasks: [ 'promo1', 'promo2', 'promo3', 'push' ]
|
||||
tasks: ['promo1', 'promo2', 'promo3', 'push']
|
||||
},
|
||||
{
|
||||
bossAppId: 'BMQAm5iUVtPsJVsU',
|
||||
titleId: '000500101004D000',
|
||||
titleRegion: 'JPN',
|
||||
name: 'Notifications',
|
||||
tasks: [ 'sysmsg1', 'sysmsg2' ]
|
||||
tasks: ['sysmsg1', 'sysmsg2']
|
||||
},
|
||||
{
|
||||
bossAppId: 'LRmanFo4Tx3kEGDp',
|
||||
titleId: '000500101004D100',
|
||||
titleRegion: 'USA',
|
||||
name: 'Notifications',
|
||||
tasks: [ 'sysmsg1', 'sysmsg2' ]
|
||||
tasks: ['sysmsg1', 'sysmsg2']
|
||||
},
|
||||
{
|
||||
bossAppId: 'TZr27FE8wzKiEaTO',
|
||||
titleId: '000500101004D200',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Notifications',
|
||||
tasks: [ 'sysmsg1', 'sysmsg2' ]
|
||||
tasks: ['sysmsg1', 'sysmsg2']
|
||||
},
|
||||
{
|
||||
bossAppId: 'JnIrm9c4E9JBmxBo',
|
||||
titleId: '0005000010185200',
|
||||
titleRegion: 'JPN',
|
||||
name: 'NewスーパーマリオブラザーズU 無料お試し版 (New SUPER MARIO BROS. U (Trial))',
|
||||
tasks: [ 'news' ]
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'dadlI27Ww8H2d56x',
|
||||
titleId: '0005000010101C00',
|
||||
titleRegion: 'JPN',
|
||||
name: 'NewスーパーマリオブラザーズU (New SUPER MARIO BROS. U)',
|
||||
tasks: [ 'news' ]
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'RaPn5saabzliYrpo',
|
||||
titleId: '0005000010101D00',
|
||||
titleRegion: 'USA',
|
||||
name: 'New SUPER MARIO BROS. U',
|
||||
tasks: [ 'news' ]
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: '14VFIK3rY2SP0WRE',
|
||||
titleId: '0005000010101E00',
|
||||
titleRegion: 'EUR',
|
||||
name: 'New SUPER MARIO BROS. U',
|
||||
tasks: [ 'news' ]
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'RbEQ44t2AocC4rvu',
|
||||
titleId: '000500001014B700',
|
||||
titleRegion: 'USA',
|
||||
name: 'New SUPER MARIO BROS. U + New SUPER LUIGI U',
|
||||
tasks: [ 'news' ]
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: '287gv3WZdxo1QRhl',
|
||||
titleId: '000500001014B800',
|
||||
titleRegion: 'EUR',
|
||||
name: 'New SUPER MARIO BROS. U + New SUPER LUIGI U',
|
||||
tasks: [ 'news' ]
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'bb6tOEckvgZ50ciH',
|
||||
titleId: '0005000010162B00',
|
||||
titleRegion: 'JPN',
|
||||
name: 'スプラトゥーン (Splatoon)',
|
||||
tasks: [ 'optdat2', 'schdat2', 'schdata' ]
|
||||
tasks: ['optdat2', 'schdat2', 'schdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'rjVlM7hUXPxmYQJh',
|
||||
titleId: '0005000010176900',
|
||||
titleRegion: 'USA',
|
||||
name: 'Splatoon',
|
||||
tasks: [ 'optdat2', 'schdat2', 'schdata', 'optdata2', 'schdata2' ]
|
||||
tasks: ['optdat2', 'schdat2', 'schdata', 'optdata2', 'schdata2']
|
||||
},
|
||||
{
|
||||
bossAppId: 'zvGSM4kOrXpkKnpT',
|
||||
titleId: '0005000010176A00',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Splatoon',
|
||||
tasks: [ 'optdat2', 'schdat2', 'schdata', 'optdata' ]
|
||||
tasks: ['optdat2', 'schdat2', 'schdata', 'optdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'm8KJPtmPweiPuETE',
|
||||
titleId: '000500001012F100',
|
||||
titleRegion: 'JPN',
|
||||
name: 'Wii Sports Club',
|
||||
tasks: [ 'sp1_ans' ]
|
||||
tasks: ['sp1_ans']
|
||||
},
|
||||
{
|
||||
bossAppId: 'pO72Hi5uqf5yuNd8',
|
||||
titleId: '0005000010144D00',
|
||||
titleRegion: 'USA',
|
||||
name: 'Wii Sports Club',
|
||||
tasks: [ 'sp1_ans' ]
|
||||
tasks: ['sp1_ans']
|
||||
},
|
||||
{
|
||||
bossAppId: '4m8Xme1wKgzwslTJ',
|
||||
titleId: '0005000010144E00',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Wii Sports Club',
|
||||
tasks: [ 'sp1_ans' ]
|
||||
tasks: ['sp1_ans']
|
||||
},
|
||||
{
|
||||
bossAppId: 'ESLqtAhxS8KQU4eu',
|
||||
titleId: '000500001018DB00',
|
||||
titleRegion: 'JPN',
|
||||
name: 'Super Mario Maker (スーパーマリオメーカー)',
|
||||
tasks: [ 'CHARA' ]
|
||||
tasks: ['CHARA']
|
||||
},
|
||||
{
|
||||
bossAppId: 'vGwChBW1ExOoHDsm',
|
||||
titleId: '000500001018DC00',
|
||||
titleRegion: 'USA',
|
||||
name: 'Super Mario Maker',
|
||||
tasks: [ 'CHARA' ]
|
||||
tasks: ['CHARA']
|
||||
},
|
||||
{
|
||||
bossAppId: 'IeUc4hQsKKe9rJHB',
|
||||
titleId: '000500001018DD00',
|
||||
titleRegion: 'EUA',
|
||||
name: 'Super Mario Maker',
|
||||
tasks: [ 'CHARA' ]
|
||||
tasks: ['CHARA']
|
||||
},
|
||||
{
|
||||
bossAppId: '4krJA4Gx3jF5nhQf',
|
||||
titleId: '000500001012BC00',
|
||||
titleRegion: 'JPN',
|
||||
name: 'ピクミン3 (PIKMIN 3)',
|
||||
tasks: [ 'histgrm' ]
|
||||
tasks: ['histgrm']
|
||||
},
|
||||
{
|
||||
bossAppId: '9jRZEoWYLc3OG9a8',
|
||||
titleId: '000500001012BD00',
|
||||
titleRegion: 'USA',
|
||||
name: 'PIKMIN 3',
|
||||
tasks: [ 'histgrm' ]
|
||||
tasks: ['histgrm']
|
||||
},
|
||||
{
|
||||
bossAppId: 'VWqUTspR5YtjDjxa',
|
||||
titleId: '000500001012BE00',
|
||||
titleRegion: 'EUR',
|
||||
name: 'PIKMIN 3',
|
||||
tasks: [ 'histgrm' ]
|
||||
tasks: ['histgrm']
|
||||
},
|
||||
{
|
||||
bossAppId: 'Ge1KtMu8tYlf4AUM',
|
||||
titleId: '0005000010192000',
|
||||
titleRegion: 'JPN',
|
||||
name: '太鼓の達人 特盛り! (Taiko no Tatsujin Tokumori!)',
|
||||
tasks: [ 'notice1' ]
|
||||
tasks: ['notice1']
|
||||
},
|
||||
{
|
||||
bossAppId: 'gycVtTzCouZmukZ6',
|
||||
titleId: '0005000010110E00',
|
||||
titleRegion: 'JPN',
|
||||
name: '大乱闘スマッシュブラザーズ for Wii U (Super Smash Bros. for Wii U)',
|
||||
tasks: [ 'NEWS', 'amiibo' ]
|
||||
tasks: ['NEWS', 'amiibo']
|
||||
},
|
||||
{
|
||||
bossAppId: 'o2Ug1pIp9Uhri6Nh',
|
||||
titleId: '0005000010144F00',
|
||||
titleRegion: 'USA',
|
||||
name: 'Super Smash Bros. for Wii U',
|
||||
tasks: [ 'amiibo', 'NEWS', 'friend', 'CONQ' ]
|
||||
tasks: ['amiibo', 'NEWS', 'friend', 'CONQ']
|
||||
},
|
||||
{
|
||||
bossAppId: 'n6rAJ1nnfC1Sgcpl',
|
||||
titleId: '0005000010145000',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Super Smash Bros. for Wii U',
|
||||
tasks: [ 'amiibo', 'NEWS', 'friend', 'CONQ' ]
|
||||
tasks: ['amiibo', 'NEWS', 'friend', 'CONQ']
|
||||
},
|
||||
{
|
||||
bossAppId: 'CHUN6T1m7Xk4EBg4',
|
||||
titleId: '00050000101DFF00',
|
||||
titleRegion: 'JPN',
|
||||
name: 'プチコンBIG (Petitcom BIG)',
|
||||
tasks: [ 'ptcbnws' ]
|
||||
tasks: ['ptcbnws']
|
||||
},
|
||||
{
|
||||
bossAppId: 'zyXdCW9jGdi9rjaz',
|
||||
titleId: '0005000010142200',
|
||||
titleRegion: 'JPN',
|
||||
name: 'NewスーパールイージU (New SUPER LUIGI U)',
|
||||
tasks: [ 'news' ]
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'jPHLlJr2fJyTzffp',
|
||||
titleId: '0005000010142300',
|
||||
titleRegion: 'USA',
|
||||
name: 'New SUPER LUIGI U',
|
||||
tasks: [ 'news' ]
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'YsXB6IRGSI56tPxl',
|
||||
titleId: '0005000010142400',
|
||||
titleRegion: 'EUR',
|
||||
name: 'New SUPER LUIGI U',
|
||||
tasks: [ 'news' ]
|
||||
tasks: ['news']
|
||||
},
|
||||
{
|
||||
bossAppId: 'Lbqp9Sg1i0xUzFFa',
|
||||
titleId: '0005000010113800',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Zen Pinball 2',
|
||||
tasks: [ 'PTS' ]
|
||||
tasks: ['PTS']
|
||||
},
|
||||
{
|
||||
bossAppId: 'DwU7n0FidGrLNiOo',
|
||||
titleId: '000500001014D900',
|
||||
titleRegion: 'JPN',
|
||||
name: 'ぷよぷよテトリス (PUYOPUYOTETRIS)',
|
||||
tasks: [ 'boss1', 'boss2', 'boss3' ]
|
||||
tasks: ['boss1', 'boss2', 'boss3']
|
||||
},
|
||||
{
|
||||
bossAppId: 'yIUkFmuGVkGP8pDb',
|
||||
titleId: '0005000010132200',
|
||||
titleRegion: 'JPN',
|
||||
name: '太鼓の達人 Wii Uば~じょん! (Taiko no Tatsujin Wii U version!)',
|
||||
tasks: [ 'notice1' ]
|
||||
tasks: ['notice1']
|
||||
},
|
||||
{
|
||||
bossAppId: 'v4WRObSzD7VU3dcJ',
|
||||
titleId: '00050000101D3000',
|
||||
titleRegion: 'JPN',
|
||||
name: '太鼓の達人 あつめて★ともだち大作戦! (Taiko no Tatsujin Atsumete★ TomodachiDaisakusen!)',
|
||||
tasks: [ 'notice1' ]
|
||||
tasks: ['notice1']
|
||||
},
|
||||
{
|
||||
bossAppId: '3zDjXIA57bSceyaw',
|
||||
titleId: '00050000101BEC00',
|
||||
titleRegion: 'USA',
|
||||
name: 'Star Fox Guard',
|
||||
tasks: [ 'param' ]
|
||||
tasks: ['param']
|
||||
},
|
||||
{
|
||||
bossAppId: 'NL38jhExI2CQqhWd',
|
||||
titleId: '00050000101CDB00',
|
||||
titleRegion: 'JPN',
|
||||
name: 'Splatoon Pre-Launch Review',
|
||||
tasks: [ 'schdata' ]
|
||||
tasks: ['schdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'sE6KwEpQYyg6tdU7',
|
||||
titleId: '00050000101CDC00',
|
||||
titleRegion: 'USA',
|
||||
name: 'Splatoon Pre-Launch Review',
|
||||
tasks: [ 'schdata' ]
|
||||
tasks: ['schdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'pTKZ9q5KrCP3gBag',
|
||||
titleId: '00050000101CDD00',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Splatoon Pre-Launch Review',
|
||||
tasks: [ 'schdata' ]
|
||||
tasks: ['schdata']
|
||||
},
|
||||
{
|
||||
bossAppId: 'CJT88RO008LAnD51',
|
||||
titleId: '0005000010170600',
|
||||
titleRegion: 'JPN',
|
||||
name: '仮面ライダー バトライド・ウォーⅡ プレミアムTV&MOVIEサウンドED. (KAMEN RIDER BATTRIDE WAR Ⅱ PREMIUM TV&MOVIE SOUND ED.)',
|
||||
tasks: [ 'PE_GAK', 'PE_ZNG' ]
|
||||
tasks: ['PE_GAK', 'PE_ZNG']
|
||||
},
|
||||
{
|
||||
bossAppId: 'FyyMFzEByuQJc6sJ',
|
||||
titleId: '0005000010135200',
|
||||
titleRegion: 'USA',
|
||||
name: 'Star Wars Pinball',
|
||||
tasks: [ 'PTS' ]
|
||||
tasks: ['PTS']
|
||||
},
|
||||
{
|
||||
bossAppId: 'A4yyXWKZZUToFtrt',
|
||||
titleId: '0005000010132A00',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Star Wars Pinball',
|
||||
tasks: [ 'PTS' ]
|
||||
tasks: ['PTS']
|
||||
},
|
||||
{
|
||||
bossAppId: 'HauaFQ1sPsnQ6rBj',
|
||||
titleId: '0005000010171F00',
|
||||
titleRegion: 'USA',
|
||||
name: 'Pushmo World',
|
||||
tasks: [ 'annouce' ]
|
||||
tasks: ['annouce']
|
||||
},
|
||||
{
|
||||
bossAppId: 'qDUeFmk0Az71nHyD',
|
||||
titleId: '0005000010110900',
|
||||
titleRegion: 'JPN',
|
||||
name: 'NINJA GAIDEN 3: Razor\'s Edge',
|
||||
tasks: [ 'DLCINFO' ]
|
||||
tasks: ['DLCINFO']
|
||||
},
|
||||
{
|
||||
bossAppId: 'yVsSPM2E0DEOxroT',
|
||||
titleId: '0005000010110A00',
|
||||
titleRegion: 'USA',
|
||||
name: 'NINJA GAIDEN 3: Razor\'s Edge',
|
||||
tasks: [ 'DLCINFO' ]
|
||||
tasks: ['DLCINFO']
|
||||
},
|
||||
{
|
||||
bossAppId: 'Xw6OvZkQofQ3O8Bi',
|
||||
titleId: '0005000010110B00',
|
||||
titleRegion: 'EUR',
|
||||
name: 'Ninja Gaiden 3: Razor\'s Edge',
|
||||
tasks: [ 'DLCINFO' ]
|
||||
tasks: ['DLCINFO']
|
||||
},
|
||||
{
|
||||
bossAppId: 'LUQX5swEjBUPQ8nR',
|
||||
titleId: '0005000010110200',
|
||||
titleRegion: 'USA',
|
||||
name: 'WARRIORS OROCHI 3 Hyper(NA)',
|
||||
tasks: [ 'OR2H000' ]
|
||||
tasks: ['OR2H000']
|
||||
},
|
||||
{
|
||||
bossAppId: 'y4pXrgLe0JGao3No',
|
||||
titleId: '0005000010112B00',
|
||||
titleRegion: 'EUR',
|
||||
name: 'WARRIORS OROCHI 3 Hyper(EU)',
|
||||
tasks: [ 'OR2H000' ]
|
||||
tasks: ['OR2H000']
|
||||
},
|
||||
{
|
||||
bossAppId: 'j01mRJ9sNe00MWPC',
|
||||
titleId: '0005000010170700',
|
||||
titleRegion: 'JPN',
|
||||
name: '仮面ライダー バトライド・ウォーⅡ (KAMEN RIDER BATTRIDE WAR Ⅱ)',
|
||||
tasks: [ 'CHR_GAK', 'CHR_ZNG' ]
|
||||
tasks: ['CHR_GAK', 'CHR_ZNG']
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ListTasksResponse } from '@pretendonetwork/grpc/boss/list_tasks';
|
||||
import { getAllTasks } from '@/database';
|
||||
import type { ListTasksResponse } from '@pretendonetwork/grpc/boss/list_tasks';
|
||||
|
||||
export async function listTasks(): Promise<ListTasksResponse> {
|
||||
const tasks = await getAllTasks(false);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { Status, ServerMiddlewareCall, CallContext, ServerError } from 'nice-grpc';
|
||||
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,
|
||||
context: CallContext
|
||||
): AsyncGenerator<Response, Response | void, undefined> {
|
||||
const apiKey: string | undefined = context.metadata.get('X-API-Key');
|
||||
|
||||
|
|
@ -12,4 +13,4 @@ export async function* apiKeyMiddleware<Request, Response>(
|
|||
}
|
||||
|
||||
return yield* call.next(call.request, context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { Status, ServerMiddlewareCall, CallContext, ServerError } from 'nice-grpc';
|
||||
import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
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';
|
||||
|
||||
const TOKEN_REQUIRED_PATHS = [
|
||||
'/boss.BOSS/RegisterTask',
|
||||
'/boss.BOSS/UpdateTask',
|
||||
'/boss.BOSS/DeleteTask',
|
||||
'/boss.BOSS/UploadFile',
|
||||
'/boss.BOSS/DeleteFile',
|
||||
'/boss.BOSS/DeleteFile'
|
||||
];
|
||||
|
||||
export type AuthenticationCallContextExt = {
|
||||
|
|
@ -16,7 +17,7 @@ export type AuthenticationCallContextExt = {
|
|||
|
||||
export async function* authenticationMiddleware<Request, Response>(
|
||||
call: ServerMiddlewareCall<Request, Response, AuthenticationCallContextExt>,
|
||||
context: CallContext,
|
||||
context: CallContext
|
||||
): AsyncGenerator<Response, Response | void, undefined> {
|
||||
const token: string | undefined = context.metadata.get('X-Token')?.trim();
|
||||
|
||||
|
|
@ -50,4 +51,4 @@ export async function* authenticationMiddleware<Request, Response>(
|
|||
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { CallContext, Status, ServerError } from 'nice-grpc';
|
||||
import { RegisterTaskRequest, RegisterTaskResponse } from '@pretendonetwork/grpc/boss/register_task';
|
||||
import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import { ServerError, Status } from 'nice-grpc';
|
||||
import { getTask } from '@/database';
|
||||
import { Task } from '@/models/task';
|
||||
import { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import type { RegisterTaskRequest, RegisterTaskResponse } from '@pretendonetwork/grpc/boss/register_task';
|
||||
|
||||
const BOSS_APP_ID_FILTER_REGEX = /^[A-Za-z0-9]*$/;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { CallContext, Status, ServerError } from 'nice-grpc';
|
||||
import { UpdateFileMetadataRequest } from '@pretendonetwork/grpc/boss/update_file_metadata';
|
||||
import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getTaskFileByDataID } from '@/database';
|
||||
import { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty';
|
||||
import { isValidFileNotifyCondition, isValidFileType } from '@/util';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { UpdateFileMetadataRequest } from '@pretendonetwork/grpc/boss/update_file_metadata';
|
||||
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import type { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty';
|
||||
|
||||
export async function updateFileMetadata(request: UpdateFileMetadataRequest, context: CallContext & AuthenticationCallContextExt): Promise<Empty> {
|
||||
// * This is asserted in authentication middleware, we know this is never null
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { CallContext, Status, ServerError } from 'nice-grpc';
|
||||
import { UpdateTaskRequest } from '@pretendonetwork/grpc/boss/update_task';
|
||||
import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getTask } from '@/database';
|
||||
import { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { UpdateTaskRequest } from '@pretendonetwork/grpc/boss/update_task';
|
||||
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import type { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty';
|
||||
|
||||
export async function updateTask(request: UpdateTaskRequest, context: CallContext & AuthenticationCallContextExt): Promise<Empty> {
|
||||
// * This is asserted in authentication middleware, we know this is never null
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { CallContext, Status, ServerError } from 'nice-grpc';
|
||||
import { UploadFileRequest, UploadFileResponse } from '@pretendonetwork/grpc/boss/upload_file';
|
||||
import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { encryptWiiU } from '@pretendonetwork/boss-crypto';
|
||||
import { isValidCountryCode, isValidFileNotifyCondition, isValidFileType, isValidLanguage, md5, uploadCDNFile } from '@/util';
|
||||
import { getTask, getTaskFile } from '@/database';
|
||||
import { File } from '@/models/file';
|
||||
import { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import { config } from '@/config-manager';
|
||||
import type { CallContext } from 'nice-grpc';
|
||||
import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import type { UploadFileRequest, UploadFileResponse } from '@pretendonetwork/grpc/boss/upload_file';
|
||||
|
||||
const BOSS_APP_ID_FILTER_REGEX = /^[A-Za-z0-9]*$/;
|
||||
|
||||
|
|
@ -172,7 +173,7 @@ export async function uploadFile(request: UploadFileRequest, context: CallContex
|
|||
notifyOnNew: file.notify_on_new,
|
||||
notifyLed: file.notify_led,
|
||||
createdTimestamp: file.created,
|
||||
updatedTimestamp: file.updated,
|
||||
updatedTimestamp: file.updated
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { createServer, Server } from 'nice-grpc';
|
||||
import { createServer } from 'nice-grpc';
|
||||
import { BOSSDefinition } from '@pretendonetwork/grpc/boss/boss_service';
|
||||
import { apiKeyMiddleware } from '@/services/grpc/boss/middleware/api-key-middleware';
|
||||
import { authenticationMiddleware } from '@/services/grpc/boss/middleware/authentication-middleware';
|
||||
import { implementation } from '@/services/grpc/boss/implementation';
|
||||
import { config } from '@/config-manager';
|
||||
import type { Server } from 'nice-grpc';
|
||||
|
||||
export async function startGRPCServer(): Promise<void> {
|
||||
const server: Server = createServer();
|
||||
|
|
@ -11,4 +12,4 @@ export async function startGRPCServer(): Promise<void> {
|
|||
server.with(apiKeyMiddleware).with(authenticationMiddleware).add(BOSSDefinition, implementation);
|
||||
|
||||
await server.listen(`${config.grpc.boss.address}:${config.grpc.boss.port}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import path from 'node:path';
|
||||
import fs from 'fs-extra';
|
||||
import express from 'express';
|
||||
import subdomain from 'express-subdomain';
|
||||
import { fileErrCallback } from '@/util';
|
||||
|
||||
const npdi = express.Router();
|
||||
|
||||
|
|
@ -9,19 +9,18 @@ npdi.get('/p01/data/1/:titleHash/:dataID/:fileHash', (request, response) => {
|
|||
const { titleHash, fileHash } = request.params;
|
||||
const contentPath = path.normalize(`${__dirname}/../../cdn/content/encrypted/${titleHash}/${fileHash}`);
|
||||
|
||||
if (fs.existsSync(contentPath)) {
|
||||
response.set('Content-Type', 'applicatoin/octet-stream');
|
||||
response.set('Content-Disposition', 'attachment');
|
||||
response.set('Content-Transfer-Encoding', 'binary');
|
||||
response.set('Content-Type', 'applicatoin/octet-stream');
|
||||
response.sendFile(contentPath);
|
||||
} else {
|
||||
response.sendStatus(404);
|
||||
}
|
||||
response.sendFile(contentPath, {
|
||||
headers: {
|
||||
// * The misspelling here is intentional, it's what the official server sets
|
||||
'Content-Type': 'applicatoin/octet-stream',
|
||||
'Content-Disposition': 'attachment',
|
||||
'Content-Transfer-Encoding': 'binary'
|
||||
}
|
||||
}, fileErrCallback(response));
|
||||
});
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(subdomain('npdi.cdn', npdi));
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { Stream } from 'node:stream';
|
||||
import express from 'express';
|
||||
import subdomain from 'express-subdomain';
|
||||
import { getTaskFile } from '@/database';
|
||||
import { getCDNFileStream } from '@/util';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
|
||||
const npdl = express.Router();
|
||||
|
||||
|
|
@ -16,9 +18,9 @@ npdl.get([
|
|||
languageCode?: string;
|
||||
fileName: string;
|
||||
}, any, any, {
|
||||
ap?: string;
|
||||
tm?: string;
|
||||
}>, response) => {
|
||||
ap?: string;
|
||||
tm?: string;
|
||||
}>, response) => {
|
||||
const { appID, taskID, countryCode, languageCode, fileName } = request.params;
|
||||
|
||||
const file = await getTaskFile(appID, taskID, fileName, countryCode, languageCode);
|
||||
|
|
@ -37,7 +39,13 @@ npdl.get([
|
|||
}
|
||||
|
||||
response.setHeader('Last-Modified', new Date(Number(file.updated)).toUTCString());
|
||||
readStream.pipe(response);
|
||||
|
||||
Stream.pipeline(readStream, response, (err) => {
|
||||
if (err) {
|
||||
LOG_ERROR('Error with response stream: ' + err.message);
|
||||
response.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const router = express.Router();
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ npfl.get('/p01/filelist/:appID/:taskID', async (request: express.Request<{
|
|||
appID: string;
|
||||
taskID: string;
|
||||
}, any, any, {
|
||||
c?: string;
|
||||
l?: string;
|
||||
a1?: string;
|
||||
a2?: string;
|
||||
a3?: string;
|
||||
ap?: string;
|
||||
tm?: string;
|
||||
}>, response) => {
|
||||
c?: string;
|
||||
l?: string;
|
||||
a1?: string;
|
||||
a2?: string;
|
||||
a3?: string;
|
||||
ap?: string;
|
||||
tm?: string;
|
||||
}>, response) => {
|
||||
for (const param in request.query) {
|
||||
// * Beats me why the real server does this.
|
||||
// * Just doing accurate emulation ¯\_(ツ)_/¯
|
||||
|
|
@ -87,7 +87,7 @@ npfl.get('/p01/filelist/:appID/:taskID', async (request: express.Request<{
|
|||
file.attribute2,
|
||||
file.attribute3,
|
||||
file.size,
|
||||
file.updated/1000n // * Expects time as seconds, not milliseconds
|
||||
file.updated / 1000n // * Expects time as seconds, not milliseconds
|
||||
];
|
||||
const line = `${params.join('\t')}\r\n`;
|
||||
|
||||
|
|
@ -111,4 +111,4 @@ const router = express.Router();
|
|||
|
||||
router.use(subdomain('npfl.c.app', npfl));
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import xmlbuilder from 'xmlbuilder';
|
|||
import moment from 'moment';
|
||||
import express from 'express';
|
||||
import subdomain from 'express-subdomain';
|
||||
import { PolicyList } from '@/types/common/policylist';
|
||||
import type { PolicyList } from '@/types/common/policylist';
|
||||
|
||||
const nppl = express.Router();
|
||||
|
||||
|
|
@ -186,4 +186,4 @@ const router = express.Router();
|
|||
router.use(subdomain('nppl.c.app', nppl)); // * 3DS
|
||||
router.use(subdomain('nppl.app', nppl)); // * WiiU
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import path from 'node:path';
|
||||
import fs from 'fs-extra';
|
||||
import express from 'express';
|
||||
import subdomain from 'express-subdomain';
|
||||
import { fileErrCallback } from '@/util';
|
||||
|
||||
const npts = express.Router();
|
||||
|
||||
|
|
@ -9,28 +9,26 @@ npts.get('/p01/tasksheet/:id/:hash/:fileName', (request, response) => {
|
|||
const { id, hash, fileName } = request.params;
|
||||
const tasksheetPath = path.normalize(`${__dirname}/../../cdn/tasksheet/${id}/${hash}/${fileName}`);
|
||||
|
||||
if (fs.existsSync(tasksheetPath)) {
|
||||
response.set('Content-Type', 'text/xml');
|
||||
response.sendFile(tasksheetPath);
|
||||
} else {
|
||||
response.sendStatus(404);
|
||||
}
|
||||
response.sendFile(tasksheetPath, {
|
||||
headers: {
|
||||
'Content-Type': 'text/xml'
|
||||
}
|
||||
}, fileErrCallback(response));
|
||||
});
|
||||
|
||||
npts.get('/p01/tasksheet/:id/:hash/:subfolder/:fileName', (request, response) => {
|
||||
const { id, hash, subfolder, fileName } = request.params;
|
||||
const tasksheetPath = path.normalize(`${__dirname}/../../cdn/tasksheet/${id}/${hash}/_subfolder/${subfolder}/${fileName}`);
|
||||
|
||||
if (fs.existsSync(tasksheetPath)) {
|
||||
response.set('Content-Type', 'text/xml');
|
||||
response.sendFile(tasksheetPath);
|
||||
} else {
|
||||
response.sendStatus(404);
|
||||
}
|
||||
response.sendFile(tasksheetPath, {
|
||||
headers: {
|
||||
'Content-Type': 'text/xml'
|
||||
}
|
||||
}, fileErrCallback(response));
|
||||
});
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(subdomain('npts.app', npts));
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import { getDuplicateCECData, getRandomCECData } from '@/database';
|
|||
import { getFriends } from '@/util';
|
||||
import { CECData } from '@/models/cec-data';
|
||||
import { CECSlot } from '@/models/cec-slot';
|
||||
import { SendMode, SPRSlot } from '@/types/common/spr-slot';
|
||||
import { SendMode } from '@/types/common/spr-slot';
|
||||
import type { SPRSlot } from '@/types/common/spr-slot';
|
||||
|
||||
const spr = express.Router();
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ function multipartParser(request: express.Request, response: express.Response, n
|
|||
let fileBuffer = Buffer.alloc(0);
|
||||
let fileName = '';
|
||||
|
||||
part.on('header', header => {
|
||||
part.on('header', (header) => {
|
||||
const contentDisposition = header['content-disposition' as keyof object];
|
||||
const regexResult = RE_FILE_NAME.exec(contentDisposition);
|
||||
|
||||
|
|
@ -202,7 +203,7 @@ spr.post('/relay/0', multipartParser, async (request, response) => {
|
|||
sendMode,
|
||||
gameID,
|
||||
size,
|
||||
data,
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +235,7 @@ spr.post('/relay/0', multipartParser, async (request, response) => {
|
|||
await CECSlot.findOneAndUpdate({
|
||||
creator_pid: request.pid,
|
||||
game_id: slotData.game_id
|
||||
}, {latest_data_id: slotData.id}, {upsert: true});
|
||||
}, { latest_data_id: slotData.id }, { upsert: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +265,6 @@ spr.post('/relay/0', multipartParser, async (request, response) => {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
response.send(sprData);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import mongoose from 'mongoose';
|
||||
import type mongoose from 'mongoose';
|
||||
|
||||
export interface DisabledFeatures {
|
||||
s3: boolean
|
||||
s3: boolean;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ export type PolicyList = {
|
|||
Persistent?: boolean;
|
||||
Revive?: boolean;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export enum CTRSystemModel {
|
|||
KTR, // * New Nintendo 3DS
|
||||
FTR, // * Nintendo 2DS
|
||||
RED, // * New Nintendo 3DS XL
|
||||
JAN // * New Nintendo 2DS XL
|
||||
JAN // * New Nintendo 2DS XL
|
||||
}
|
||||
|
||||
export type UserAgentInfo = {
|
||||
|
|
|
|||
2
src/types/express-subdomain.d.ts
vendored
2
src/types/express-subdomain.d.ts
vendored
|
|
@ -13,4 +13,4 @@ declare module 'express-subdomain'{
|
|||
subdomain: string,
|
||||
fn: Router | ((req: Request, res: Response) => void | any)
|
||||
): (req: Request, res: Response, next: () => void) => void | typeof fn;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
src/types/express.d.ts
vendored
2
src/types/express.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
|||
import { GetNEXDataResponse } from '@pretendonetwork/grpc/account/get_nex_data_rpc';
|
||||
import type { GetNEXDataResponse } from '@pretendonetwork/grpc/account/get_nex_data_rpc';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Model, HydratedDocument } from 'mongoose';
|
||||
import type { Model, HydratedDocument } from 'mongoose';
|
||||
|
||||
export interface ICECData {
|
||||
creator_pid: number;
|
||||
|
|
@ -13,6 +13,6 @@ export interface ICECDataMethods {}
|
|||
|
||||
interface ICECDataQueryHelpers {}
|
||||
|
||||
export interface CECDataModel extends Model<ICECData, ICECDataQueryHelpers, ICECDataMethods> {}
|
||||
export type CECDataModel = Model<ICECData, ICECDataQueryHelpers, ICECDataMethods>;
|
||||
|
||||
export type HydratedCECDataDocument = HydratedDocument<ICECData, ICECDataMethods>
|
||||
export type HydratedCECDataDocument = HydratedDocument<ICECData, ICECDataMethods>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Model, HydratedDocument } from 'mongoose';
|
||||
import type { Model, HydratedDocument } from 'mongoose';
|
||||
|
||||
export interface ICECSlot {
|
||||
creator_pid: number;
|
||||
|
|
@ -10,6 +10,6 @@ export interface ICECSlotMethods {}
|
|||
|
||||
interface ICECSlotQueryHelpers {}
|
||||
|
||||
export interface CECSlotModel extends Model<ICECSlot, ICECSlotQueryHelpers, ICECSlotMethods> {}
|
||||
export type CECSlotModel = Model<ICECSlot, ICECSlotQueryHelpers, ICECSlotMethods>;
|
||||
|
||||
export type HydratedCECSlotDocument = HydratedDocument<ICECSlot, ICECSlotMethods>
|
||||
export type HydratedCECSlotDocument = HydratedDocument<ICECSlot, ICECSlotMethods>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Model, HydratedDocument } from 'mongoose';
|
||||
import type { Model, HydratedDocument } from 'mongoose';
|
||||
|
||||
export interface IFile {
|
||||
deleted: boolean;
|
||||
|
|
@ -26,6 +26,6 @@ export interface IFileMethods {}
|
|||
|
||||
interface IFileQueryHelpers {}
|
||||
|
||||
export interface FileModel extends Model<IFile, IFileQueryHelpers, IFileMethods> {}
|
||||
export type FileModel = Model<IFile, IFileQueryHelpers, IFileMethods>;
|
||||
|
||||
export type HydratedFileDocument = HydratedDocument<IFile, IFileMethods>
|
||||
export type HydratedFileDocument = HydratedDocument<IFile, IFileMethods>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Model, HydratedDocument } from 'mongoose';
|
||||
import type { Model, HydratedDocument } from 'mongoose';
|
||||
|
||||
export interface ITask {
|
||||
deleted: boolean;
|
||||
|
|
@ -6,17 +6,17 @@ export interface ITask {
|
|||
in_game_id: string;
|
||||
boss_app_id: string;
|
||||
creator_pid: number;
|
||||
status: 'open'; // TODO - Make this a union. What else is there?
|
||||
status: 'open'; // TODO - Make this a union. What else is there?
|
||||
title_id: string;
|
||||
description: string;
|
||||
created: bigint;
|
||||
updated: bigint;
|
||||
}
|
||||
|
||||
export interface ITaskMethods {}
|
||||
export interface ITaskMethods { }
|
||||
|
||||
interface ITaskQueryHelpers {}
|
||||
interface ITaskQueryHelpers { }
|
||||
|
||||
export interface TaskModel extends Model<ITask, ITaskQueryHelpers, ITaskMethods> {}
|
||||
export type TaskModel = Model<ITask, ITaskQueryHelpers, ITaskMethods>;
|
||||
|
||||
export type HydratedTaskDocument = HydratedDocument<ITask, ITaskMethods>
|
||||
export type HydratedTaskDocument = HydratedDocument<ITask, ITaskMethods>;
|
||||
|
|
|
|||
41
src/util.ts
41
src/util.ts
|
|
@ -1,17 +1,23 @@
|
|||
import crypto from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import { Readable } from 'node:stream';
|
||||
import fs from 'fs-extra';
|
||||
import { createChannel, createClient, Metadata } from 'nice-grpc';
|
||||
import { GetObjectCommand, PutObjectCommand, S3 } from '@aws-sdk/client-s3';
|
||||
import { AccountClient, AccountDefinition } from '@pretendonetwork/grpc/account/account_service';
|
||||
import { FriendsClient, FriendsDefinition } from '@pretendonetwork/grpc/friends/friends_service';
|
||||
import { GetNEXDataResponse } from '@pretendonetwork/grpc/account/get_nex_data_rpc';
|
||||
import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import { GetUserFriendPIDsResponse } from '@pretendonetwork/grpc/friends/get_user_friend_pids_rpc';
|
||||
import { AccountDefinition } from '@pretendonetwork/grpc/account/account_service';
|
||||
import { FriendsDefinition } from '@pretendonetwork/grpc/friends/friends_service';
|
||||
import { config, disabledFeatures } from '@/config-manager';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
import type { FriendsClient } from '@pretendonetwork/grpc/friends/friends_service';
|
||||
import type { AccountClient } from '@pretendonetwork/grpc/account/account_service';
|
||||
import type { S3Client } from '@aws-sdk/client-s3';
|
||||
import type { GetNEXDataResponse } from '@pretendonetwork/grpc/account/get_nex_data_rpc';
|
||||
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
import type { GetUserFriendPIDsResponse } from '@pretendonetwork/grpc/friends/get_user_friend_pids_rpc';
|
||||
import type { NodeJsClient } from '@smithy/types';
|
||||
import type { Response } from 'express';
|
||||
import type { Readable } from 'node:stream';
|
||||
|
||||
let s3: S3;
|
||||
let s3: NodeJsClient<S3Client>;
|
||||
|
||||
if (!disabledFeatures.s3) {
|
||||
s3 = new S3({
|
||||
|
|
@ -57,6 +63,21 @@ const VALID_FILE_NOTIFY_CONDITIONS = [
|
|||
'app', 'account'
|
||||
];
|
||||
|
||||
export function fileErrCallback(response: Response) {
|
||||
return (err: NodeJS.ErrnoException): void => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
response.sendStatus(404);
|
||||
} else {
|
||||
if (!response.headersSent) {
|
||||
response.status(500).send('Server Error');
|
||||
}
|
||||
LOG_ERROR('Error in sending file: ' + err.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function md5(input: crypto.BinaryLike): string {
|
||||
return crypto.createHash('md5').update(input).digest('hex');
|
||||
}
|
||||
|
|
@ -138,7 +159,7 @@ export async function getFriends(pid: number): Promise<GetUserFriendPIDsResponse
|
|||
}
|
||||
}
|
||||
|
||||
export async function getCDNFileStream(key: string): Promise<Readable | fs.ReadStream | null> {
|
||||
export async function getCDNFileStream(key: string): Promise<Readable | null> {
|
||||
try {
|
||||
if (disabledFeatures.s3) {
|
||||
return await getLocalCDNFile(key);
|
||||
|
|
@ -152,9 +173,9 @@ export async function getCDNFileStream(key: string): Promise<Readable | fs.ReadS
|
|||
return null;
|
||||
}
|
||||
|
||||
return response.Body as Readable;
|
||||
return response.Body;
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"module": "commonjs",
|
||||
"module": "ES2022",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "src",
|
||||
|
|
@ -11,9 +11,16 @@
|
|||
"allowJs": true,
|
||||
"target": "es2022",
|
||||
"noEmitOnError": true,
|
||||
"noImplicitAny": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user