Skip to content

Commit

Permalink
feat: delete cards, disable shared cards, no need for pin to update, …
Browse files Browse the repository at this point in the history
…delete or block
  • Loading branch information
antonstjernquist committed Jan 29, 2024
1 parent aa1b9dc commit 7156ac6
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 54 deletions.
1 change: 1 addition & 0 deletions src/client/cl_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ RegisterNuiProxy(CashEvents.GetMyCash);
// Cards
RegisterNuiProxy(CardEvents.Get);
RegisterNuiProxy(CardEvents.Block);
RegisterNuiProxy(CardEvents.Delete);
RegisterNuiProxy(CardEvents.OrderPersonal);
RegisterNuiProxy(CardEvents.OrderShared);
RegisterNuiProxy(CardEvents.UpdatePin);
Expand Down
1 change: 1 addition & 0 deletions src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ if (isMocking) {
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 () => {
Expand Down
11 changes: 11 additions & 0 deletions src/server/services/card/card.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BlockCardInput,
Card,
CreateCardInput,
DeleteCardInput,
GetCardInput,
InventoryCard,
UpdateCardPinInput,
Expand Down Expand Up @@ -53,6 +54,16 @@ export class CardController {
}
}

@NetPromise(CardEvents.Delete)
async deleteCard(req: Request<DeleteCardInput>, res: Response<boolean>) {
try {
const isDeleted = await this.cardService.blockCard(req);
res({ status: 'ok', data: isDeleted });
} catch (error) {
res({ status: 'error', errorMsg: error.message });
}
}

@NetPromise(CardEvents.UpdatePin)
async updatePin(req: Request<UpdateCardPinInput>, res: Response<boolean>) {
try {
Expand Down
4 changes: 2 additions & 2 deletions src/server/services/card/card.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export class CardDB {
return await CardModel.findAll();
}

async getById(cardId: number): Promise<CardModel | null> {
return await CardModel.findOne({ where: { id: cardId } });
async getById(cardId: number, transaction?: Transaction): Promise<CardModel | null> {
return await CardModel.findOne({ where: { id: cardId }, transaction });
}

async getByAccountId(accountId: number): Promise<CardModel[]> {
Expand Down
88 changes: 70 additions & 18 deletions src/server/services/card/card.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { Request } from '@typings/http';
import { CardDB } from './card.db';
import { sequelize } from '@server/utils/pool';
import { AccountService } from '../account/account.service';
import { BalanceErrors, CardErrors, GenericErrors, UserErrors } from '@server/../../typings/Errors';
import {
AuthorizationErrors,
BalanceErrors,
CardErrors,
GenericErrors,
UserErrors,
} from '@server/../../typings/Errors';
import i18next from '@utils/i18n';
import {
BlockCardInput,
Expand Down Expand Up @@ -116,45 +122,88 @@ export class CardService {

async blockCard(req: Request<BlockCardInput>) {
this.validateCardsConfig();
logger.silly('Blocking card ..');
logger.silly(req.data);

const { cardId, pin } = req.data;
const card = await this.cardDB.getById(cardId);
const user = this.userService.getUser(req.source);
const { cardId } = req.data;

if (!card) {
throw new Error(GenericErrors.NotFound);
const t = await sequelize.transaction();
const card = await this.cardDB.getById(cardId, t);

if (card?.getDataValue('holderCitizenId') !== user.getIdentifier()) {
throw new Error(AuthorizationErrors.Forbidden);
}

if (pin !== card.getDataValue('pin')) {
throw new Error(CardErrors.InvalidPin);
if (!card) {
throw new Error(GenericErrors.NotFound);
}

try {
await card.update({ isBlocked: true });
t.commit();
logger.silly('Blocked card.');
return true;
} catch (error) {
} catch (error: unknown) {
t.rollback();
logger.error(error);
return false;
}
}

async updateCardPin(req: Request<UpdateCardPinInput>): Promise<boolean> {
async deleteCard(req: Request<BlockCardInput>) {
this.validateCardsConfig();
logger.silly('Ordering new card for source:' + req.source);
logger.silly('Deleting card ..');
logger.silly(req.data);
const user = this.userService.getUser(req.source);
const { cardId } = req.data;

const { cardId, newPin, oldPin } = req.data;
const card = await this.cardDB.getById(cardId);
const t = await sequelize.transaction();
const card = await this.cardDB.getById(cardId, t);

if (card?.getDataValue('holderCitizenId') !== user.getIdentifier()) {
throw new Error(AuthorizationErrors.Forbidden);
}

if (!card) {
throw new Error(GenericErrors.NotFound);
}

if (card.getDataValue('pin') !== oldPin) {
throw new Error(CardErrors.InvalidPin);
try {
await card.destroy();
t.commit();
logger.silly('Deleted card.');
return true;
} catch (error: unknown) {
t.rollback();
logger.error(error);
return false;
}
}

async updateCardPin(req: Request<UpdateCardPinInput>): Promise<boolean> {
this.validateCardsConfig();
logger.silly('Updating pin for card ..');
logger.silly(req.data);

const user = this.userService.getUser(req.source);
const { cardId, newPin } = req.data;

const t = await sequelize.transaction();
const card = await this.cardDB.getById(cardId, t);

if (card?.getDataValue('holderCitizenId') !== user.getIdentifier()) {
throw new Error(AuthorizationErrors.Forbidden);
}

if (!card) {
throw new Error(GenericErrors.NotFound);
}

try {
await card.update({ pin: newPin }, { transaction: t });
t.commit();
logger.silly('Updated pin.');
return true;
} catch (error) {
logger.error(error);
Expand All @@ -165,10 +214,12 @@ export class CardService {

async orderPersonalCard(req: Request<CreateCardInput>): Promise<Card | null> {
this.validateCardsConfig();
logger.debug('Ordering new card for source:' + req.source);
const { accountId, paymentAccountId, pin } = req.data;
logger.silly('Ordering new card ..');
logger.silly(req.data);

const user = this.userService.getUser(req.source);
const { accountId, paymentAccountId, pin } = req.data;

const newCardCost = config.cards?.cost;

if (!newCardCost) {
Expand Down Expand Up @@ -224,9 +275,10 @@ export class CardService {
this.giveCard(req.source, card.toJSON());

t.commit();
logger.silly('Ordered new card.');
return card.toJSON();
} catch (err) {
logger.error(err);
} catch (error: unknown) {
logger.error(error);
t.rollback();
throw new Error(i18next.t('Failed to create new account'));
}
Expand Down
5 changes: 3 additions & 2 deletions typings/BankCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ export interface CreateCardInput {
accountId: number;
paymentAccountId: number;
}

export interface BlockCardInput {
cardId: number;
pin: number;
}

export type DeleteCardInput = BlockCardInput;

export interface UpdateCardPinInput {
cardId: number;
newPin: number;
oldPin: number;
}
1 change: 1 addition & 0 deletions typings/Events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export enum CardEvents {
OrderShared = 'pefcl:orderSharedCard',
OrderPersonal = 'pefcl:orderPersonalCard',
Block = 'pefcl:blockCard',
Delete = 'pefcl:deleteCard',
UpdatePin = 'pefcl:updatePin',
NewCard = 'pefcl:newCard',
GetInventoryCards = 'pefcl:getInventoryCards',
Expand Down
19 changes: 16 additions & 3 deletions web/src/components/AccountCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import { IconButton, Skeleton, Stack } from '@mui/material';
import { ContentCopyRounded } from '@mui/icons-material';
import copy from 'copy-to-clipboard';

const Container = styled.div<{ accountType: AccountType; selected: boolean }>`
interface ContainerProps {
isDisabled: boolean;
accountType: AccountType;
selected: boolean;
}
const Container = styled.div<ContainerProps>`
user-select: none;
width: 100%;
padding: 1rem;
Expand Down Expand Up @@ -42,6 +47,12 @@ const Container = styled.div<{ accountType: AccountType; selected: boolean }>`
`
border: 2px solid ${theme.palette.primary.light};
`};
${({ isDisabled }) =>
isDisabled &&
`
opacity: 0.5;
`}
`;

const Row = styled.div`
Expand Down Expand Up @@ -78,20 +89,22 @@ type AccountCardProps = {
account: Account;
selected?: boolean;
withCopy?: boolean;
isDisabled?: boolean;
};

export const AccountCard = ({
account,
selected = false,
withCopy = false,
isDisabled = false,
...props
}: AccountCardProps) => {
const { type, id, balance, isDefault, accountName, number } = account;
const { t } = useTranslation();
const config = useConfig();

return (
<Container {...props} key={id} accountType={type} selected={selected}>
<Container {...props} key={id} accountType={type} selected={selected} isDisabled={isDisabled}>
<Row>
<Heading3>{formatMoney(balance, config.general)}</Heading3>
<Type>
Expand Down Expand Up @@ -126,7 +139,7 @@ export const AccountCard = ({

export const LoadingAccountCard = () => {
return (
<Container accountType={AccountType.Personal} selected={false}>
<Container accountType={AccountType.Personal} selected={false} isDisabled={false}>
<Row>
<Heading3>
<Skeleton variant="text" width={120} />
Expand Down
14 changes: 12 additions & 2 deletions web/src/views/Cards/CardsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AnimatePresence, motion } from 'framer-motion';
import theme from '@utils/theme';
import BankCards from './components/BankCards';
import { selectedAccountIdAtom } from '@data/cards';
import { AccountType } from '@typings/Account';

const Container = styled.div`
overflow: auto;
Expand Down Expand Up @@ -58,8 +59,17 @@ const CardsView = () => {

<CardContainer>
{accounts.map((account) => (
<div key={account.id} onClick={() => setSelectedAccountId(account.id)}>
<AccountCard account={account} selected={account.id === selectedAccountId} />
<div
key={account.id}
onClick={() =>
account.type !== AccountType.Shared && setSelectedAccountId(account.id)
}
>
<AccountCard
account={account}
selected={account.id === selectedAccountId}
isDisabled={account.type === AccountType.Shared}
/>
</div>
))}
</CardContainer>
Expand Down
6 changes: 6 additions & 0 deletions web/src/views/Cards/components/BankCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ const BankCards = ({ onSelectCardId, selectedCardId, accountId }: BankCardsProps
};

const selectedAccount = accounts.find((acc) => acc.id === selectedAccountId);
const selectedCard = cards.find((card) => card.id === selectedCardId);
const isAffordable = (selectedAccount?.balance ?? 0) > cost;

return (
Expand Down Expand Up @@ -180,11 +181,16 @@ const BankCards = ({ onSelectCardId, selectedCardId, accountId }: BankCardsProps
{Boolean(selectedCardId) && (
<Modal animate={{ x: 0 }} initial={{ x: 100 }} exit={{ x: 200, opacity: 0 }}>
<CardActions
isBlocked={selectedCard?.isBlocked}
cardId={selectedCardId}
onBlock={() => {
updateCards(accountId);
onSelectCardId(0);
}}
onDelete={() => {
updateCards(accountId);
onSelectCardId(0);
}}
/>
</Modal>
)}
Expand Down
Loading

0 comments on commit 7156ac6

Please sign in to comment.