Skip to content

Commit

Permalink
Feature/cards (#102)
Browse files Browse the repository at this point in the history
* feat(cards): get, create, block, update pin for "physical" cards

* fix: updated tsconfig & paths, added shared/constants

* feat(cards): implemented cards

* fix: exitListener, on/off

* fix: generate translations index on download

* fix(sync): await client load before emitting playerLoaded

* feat(cards): isCardsEnabled config

* fix: optional accountId on withdrawal

* fix: missing translations for client deposit & withdraw exports

* fix: fetch cards on atm, default to default acc on withdraw from ATM / Bank

* fix: fixed sync issues, reset rawAccountsAtom

* feat: delete cards, disable shared cards, no need for pin to update, delete or block
  • Loading branch information
antonstjernquist authored Oct 7, 2022
1 parent 926394d commit 7e41c4f
Show file tree
Hide file tree
Showing 56 changed files with 2,015 additions and 133 deletions.
1 change: 0 additions & 1 deletion .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ jobs:
LOCALAZY_READ_KEY: a8269809765126758267-f01743c76d6e9e434d7b4c6322938eefce8d82a559b57efc2acfcf4531d46089
run: |
yarn translations:pull
yarn translations:generate-index
- name: Install src deps
working-directory: src
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ jobs:
LOCALAZY_READ_KEY: a8269809765126758267-f01743c76d6e9e434d7b4c6322938eefce8d82a559b57efc2acfcf4531d46089
run: yarn translations:pull

- name: Generate translations
run: yarn translations:generate-index

- name: Install src deps
working-directory: src
run: yarn --frozen-lockfile
Expand Down
7 changes: 6 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"frameworkIntegration": {
"enabled": false,
"resource": "your-resource",
"syncInitialBankBalance": true
"syncInitialBankBalance": true,
"isCardsEnabled": false
},
"database": {
"profileQueries": true
Expand All @@ -24,6 +25,10 @@
"clearingNumber": 920,
"maximumNumberOfAccounts": 3
},
"cards": {
"cost": 4500,
"maxCardsPerAccount": 2
},
"atms": {
"distance": 5.0,
"props": [-870868698, -1126237515, 506770882],
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
"postinstall": "husky install && yarn setup",
"translations:generate": "yarn i18next",
"translations:generate-index": "node ./scripts/generateLocales.js",
"translations:pull": "localazy download",
"translations:pull": "localazy download && node ./scripts/generateLocales.js",
"translations:push": "localazy upload -w $LOCALAZY_WRITE_KEY -r $LOCALAZY_READ_KEY",
"setup": "yarn nx run-many --target=setup --all && yarn translations:pull && yarn translations:generate-index",
"build": "yarn nx run-many --target=build --all",
"lint": "yarn nx run-many --target=lint --all",
"dev": "yarn nx run-many --target=dev --all",
"tsc": "yarn nx run-many --target=tsc --all",
"dev": "yarn nx run-many --target=dev --all",
"dev:ingame": "yarn nx run-many --target=dev:ingame --all",
"dev:mobile": "yarn nx run-many --target=dev:mobile --all",
"pre-release": "yarn build && sh ./scripts/prerelease.sh",
Expand All @@ -39,4 +39,4 @@
"dependencies": {
"i18next-parser": "^6.0.0"
}
}
}
2 changes: 2 additions & 0 deletions shared/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const PIN_CODE_LENGTH = 4;
export const CHECK_PLAYER_LOADED_INTERVAL = 15; // ms;
9 changes: 7 additions & 2 deletions src/client/cl_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ATMInput } from '@typings/Account';
import { AccountEvents, CashEvents, InvoiceEvents } from '@typings/Events';
import { ServerPromiseResp } from '@typings/http';
import { Invoice, InvoiceOnlineInput } from '@typings/Invoice';
import { translations } from 'i18n';

export class Api {
utils: ClientUtils;
Expand Down Expand Up @@ -83,7 +84,9 @@ export class Api {
try {
const payload: ATMInput = {
amount,
message: 'Deposition',
message: translations.t('Successfully deposited {{amount}} into selected account.', {
amount,
}),
};
const response = await this.utils.emitNetPromise(AccountEvents.DepositMoney, payload);
console.log({ response });
Expand All @@ -97,7 +100,9 @@ export class Api {
try {
const payload: ATMInput = {
amount,
message: 'Withdrawal',
message: translations.t('Withdrew {{amount}} from an ATM.', {
amount,
}),
};

const response = await this.utils.emitNetPromise(AccountEvents.WithdrawMoney, payload);
Expand Down
13 changes: 13 additions & 0 deletions src/client/cl_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Broadcasts,
NUIEvents,
CashEvents,
CardEvents,
} from '@typings/Events';
import { Invoice } from '@typings/Invoice';
import { Transaction } from '@typings/Transaction';
Expand All @@ -24,6 +25,8 @@ const npwdExports = global.exports['npwd'];
const useFrameworkIntegration = config.frameworkIntegration?.enabled;
let hasNUILoaded = false;

emitNet(UserEvents.LoadClient);

RegisterNuiCB(NUIEvents.Loaded, () => {
console.debug('NUI has loaded.');
hasNUILoaded = true;
Expand Down Expand Up @@ -121,10 +124,20 @@ RegisterNuiProxy(SharedAccountEvents.GetUsers);
RegisterNuiProxy(ExternalAccountEvents.Add);
RegisterNuiProxy(ExternalAccountEvents.Get);

RegisterNuiProxy(AccountEvents.GetAtmAccount);
RegisterNuiProxy(AccountEvents.WithdrawMoney);
RegisterNuiProxy(AccountEvents.DepositMoney);
RegisterNuiProxy(CashEvents.GetMyCash);

// Cards
RegisterNuiProxy(CardEvents.Get);
RegisterNuiProxy(CardEvents.Block);
RegisterNuiProxy(CardEvents.Delete);
RegisterNuiProxy(CardEvents.OrderPersonal);
RegisterNuiProxy(CardEvents.OrderShared);
RegisterNuiProxy(CardEvents.UpdatePin);
RegisterNuiProxy(CardEvents.GetInventoryCards);

RegisterCommand(
'bank-force-load',
async () => {
Expand Down
4 changes: 3 additions & 1 deletion src/client/cl_exports.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { NUIEvents } from '@typings/Events';
import { setBankIsOpen, setAtmIsOpen } from 'client';
import { createInvoice, depositMoney, giveCash, withdrawMoney } from 'functions';

const exp = global.exports;

exp('openBank', async () => {
exp('openBank', async (accountId: number) => {
setBankIsOpen(true);
SendNUIMessage({ type: NUIEvents.SetCardId, payload: accountId });
});

exp('closeBank', async () => {
Expand Down
18 changes: 15 additions & 3 deletions src/server/globals.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const mockedResourceName = 'pefcl';

// TODO: Move this into package
const convars = {
mysql_connection_string: 'mysql://root:bruv@localhost/dev',
mysql_connection_string: 'mysql://root:root@127.0.0.1/QBCoreFramework_E05901?charset=utf8mb4',
};

const players: any = {
Expand All @@ -26,6 +26,10 @@ if (isMocking) {
const ServerEmitter = new EventEmitter().setMaxListeners(25);
const NetEmitter = new EventEmitter().setMaxListeners(25);

global.RegisterCommand = (cmd: string) => {
console.log('Registered command', cmd);
};

global.LoadResourceFile = (_resourceName: string, fileName: string) => {
const file = readFileSync(`${baseDir}/${fileName}`, 'utf-8');
return file;
Expand Down Expand Up @@ -64,15 +68,23 @@ if (isMocking) {
'your-resource': {
addCash: () => {
console.log('global.server.ts: Adding cash ..');
throw new Error('no funds');
throw new Error('adding cash');
},
getCash: () => {
console.log('global.server.ts: Getting cash ..');
return 2500;
},
removeCash: () => {
console.log('global.server.ts: Removing cash ..');
throw new Error('no funds');
throw new Error('could not remove cash');
},
giveCard: () => {
console.log('global.server.ts: Giving card ..');
throw new Error('giving card');
},
getCards: () => {
console.log('global.server.ts: Getting cards ..');
return [];
},
},
});
Expand Down
38 changes: 38 additions & 0 deletions src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './globals.server';
import { ServerPromiseResp } from '@project-error/pe-utils';
import {
AccountEvents,
CardEvents,
CashEvents,
ExternalAccountEvents,
GeneralEvents,
Expand All @@ -28,6 +29,7 @@ import { mockedResourceName } from './globals.server';
import { config } from './utils/server-config';
import { UserService } from './services/user/user.service';
import { container } from 'tsyringe';
import { CardService } from './services/card/card.service';

const hotReloadConfig = {
resourceName: GetCurrentResourceName(),
Expand Down Expand Up @@ -76,6 +78,7 @@ if (isMocking) {

app.post(...createEndpoint(UserEvents.GetUsers));
app.post(...createEndpoint(AccountEvents.GetAccounts));
app.post(...createEndpoint(AccountEvents.GetAtmAccount));
app.post(...createEndpoint(AccountEvents.DeleteAccount));
app.post(...createEndpoint(AccountEvents.SetDefaultAccount));
app.post(...createEndpoint(AccountEvents.CreateAccount));
Expand All @@ -96,6 +99,14 @@ if (isMocking) {
app.post(...createEndpoint(ExternalAccountEvents.Get));
app.post(...createEndpoint(CashEvents.GetMyCash));

// Cards
app.post(...createEndpoint(CardEvents.Get));
app.post(...createEndpoint(CardEvents.OrderPersonal));
app.post(...createEndpoint(CardEvents.UpdatePin));
app.post(...createEndpoint(CardEvents.Block));
app.post(...createEndpoint(CardEvents.Delete));
app.post(...createEndpoint(CardEvents.GetInventoryCards));

app.listen(port, async () => {
mainLogger.child({ module: 'server' }).debug(`[MOCKSERVER]: listening on port: ${port}`);

Expand Down Expand Up @@ -186,6 +197,33 @@ const debug = async () => {
// },
// source: 0,
// });

RegisterCommand(
'card',
async (src: number) => {
const exps = exports;
const QBCore = await exps['qb-core']?.GetCoreObject();

await exps['qb-core'].RemoveItem('bank_card');

const item = {
name: 'bank_card',
label: 'Bank card',
weight: 1,
type: 'item',
image: 'visacard.png',
};

await exps['qb-core'].AddItem('bank_card', item);

const cardService = container.resolve(CardService);

const res = await cardService.giveCard(src, QBCore);

console.log(res);
},
false,
);
};

on(GeneralEvents.ResourceStarted, debug);
Expand Down
4 changes: 0 additions & 4 deletions src/server/services/account/account.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,7 @@ export class AccountController {
@Export(ServerExports.WithdrawCash)
@NetPromise(AccountEvents.WithdrawMoney)
async withdrawMoney(req: Request<ATMInput>, res: Response<any>) {
const accountId = req.data.accountId;

try {
accountId &&
(await this._auth.isAuthorizedAccount(accountId, req.source, [AccountRole.Admin]));
await this._accountService.handleWithdrawMoney(req);
res({ status: 'ok', data: {} });
} catch (err) {
Expand Down
4 changes: 2 additions & 2 deletions src/server/services/account/account.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DataTypes, Model, Optional } from 'sequelize';
import { config } from '@utils/server-config';
import { Account, AccountRole, AccountType } from '@typings/Account';
import { sequelize } from '@utils/pool';
import { generateAccountNumber } from '@utils/misc';
import { generateClearingNumber } from '@utils/misc';
import { timestamps } from '../timestamps.model';
import { AccountEvents } from '@server/../../typings/Events';

Expand All @@ -22,7 +22,7 @@ AccountModel.init(
number: {
type: DataTypes.STRING,
unique: true,
defaultValue: generateAccountNumber,
defaultValue: generateClearingNumber,
},
accountName: {
type: DataTypes.STRING,
Expand Down
38 changes: 30 additions & 8 deletions src/server/services/account/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,28 @@ import {
AccountErrors,
AuthorizationErrors,
BalanceErrors,
CardErrors,
GenericErrors,
UserErrors,
} from '@typings/Errors';
import { SharedAccountDB } from '@services/accountShared/sharedAccount.db';
import { AccountEvents, Broadcasts } from '@server/../../typings/Events';
import { getFrameworkExports } from '@server/utils/frameworkIntegration';
import { Transaction } from 'sequelize/types';
import { CardDB } from '../card/card.db';

const logger = mainLogger.child({ module: 'accounts' });
const { enabled = false, syncInitialBankBalance = false } = config.frameworkIntegration ?? {};
const {
enabled = false,
syncInitialBankBalance = false,
isCardsEnabled = false,
} = config.frameworkIntegration ?? {};
const { firstAccountStartBalance } = config.accounts ?? {};
const isFrameworkIntegrationEnabled = enabled;

@singleton()
export class AccountService {
_cardDB: CardDB;
_accountDB: AccountDB;
_sharedAccountDB: SharedAccountDB;
_cashService: CashService;
Expand All @@ -58,7 +65,9 @@ export class AccountService {
userService: UserService,
cashService: CashService,
transactionService: TransactionService,
cardDB: CardDB,
) {
this._cardDB = cardDB;
this._accountDB = accountDB;
this._sharedAccountDB = sharedAccountDB;
this._cashService = cashService;
Expand Down Expand Up @@ -459,25 +468,39 @@ export class AccountService {
}

async handleWithdrawMoney(req: Request<ATMInput>) {
logger.silly(`"${req.source}" withdrawing "${req.data.amount}".`);
const amount = req.data.amount;
const { accountId, amount, cardId, cardPin } = req.data;
logger.silly(`"${req.source}" withdrawing "${amount}".`);

if (amount <= 0) {
throw new ServerError(GenericErrors.BadInput);
}

/* Only run the export when account is the default(?). Not sure about this. */
const t = await sequelize.transaction();
try {
const targetAccount = req.data.accountId
? await this._accountDB.getAccountById(req.data.accountId)
/* If framework is enabled, do a card check, otherwise continue. */
if (isFrameworkIntegrationEnabled && isCardsEnabled && cardId) {
const exports = getFrameworkExports();
const cards = exports.getCards(req.source);
const selectedCard = cards?.find((card) => card.id === cardId);

if (!selectedCard) {
throw new Error('User does not have selected card in inventory.');
}

const card = await this._cardDB.getById(selectedCard.id);
if (card?.getDataValue('pin') !== cardPin) {
throw new Error(CardErrors.InvalidPin);
}
}

const targetAccount = accountId
? await this._accountDB.getAccountById(accountId)
: await this.getDefaultAccountBySource(req.source);

if (!targetAccount) {
throw new ServerError(GenericErrors.NotFound);
}

const accountId = targetAccount.getDataValue('id') ?? 0;
const currentAccountBalance = targetAccount.getDataValue('balance');

if (currentAccountBalance < amount) {
Expand Down Expand Up @@ -815,7 +838,6 @@ export class AccountService {
type,
accountName: name,
ownerIdentifier: identifier,
isDefault: true,
});

const json = account.toJSON();
Expand Down
Loading

0 comments on commit 7e41c4f

Please sign in to comment.