Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42c7e59d63 | |||
| 192cf7d9cb |
@@ -350,6 +350,29 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/TransactionResponse"
|
$ref: "#/components/schemas/TransactionResponse"
|
||||||
|
/ledgers/{ledgerId}/balance:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- ledger
|
||||||
|
operationId: findBalanceByLedgerId
|
||||||
|
parameters:
|
||||||
|
- name: ledgerId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BalanceResponse"
|
||||||
/characters:
|
/characters:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -660,6 +683,30 @@ components:
|
|||||||
enum:
|
enum:
|
||||||
- ISK
|
- ISK
|
||||||
- ITEM
|
- ITEM
|
||||||
|
BalanceResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
iskBalance:
|
||||||
|
type: number
|
||||||
|
itemBalances:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ItemBalanceResponse"
|
||||||
|
required:
|
||||||
|
- iskBalance
|
||||||
|
- itemBalances
|
||||||
|
ItemBalanceResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
typeId:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
quantity:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required:
|
||||||
|
- quantity
|
||||||
|
- typeId
|
||||||
CharacterResponse:
|
CharacterResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
Generated
+233
-244
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
|||||||
"@vueuse/components": "^14.3.0",
|
"@vueuse/components": "^14.3.0",
|
||||||
"@vueuse/core": "^14.3.0",
|
"@vueuse/core": "^14.3.0",
|
||||||
"@vueuse/integrations": "^14.3.0",
|
"@vueuse/integrations": "^14.3.0",
|
||||||
|
"@vueuse/router": "^14.3.0",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"axios-rate-limit": "^1.3.1",
|
"axios-rate-limit": "^1.3.1",
|
||||||
"gemory": "file:",
|
"gemory": "file:",
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ import type { RequestArgs } from './base';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base';
|
import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base';
|
||||||
|
|
||||||
|
export interface BalanceResponse {
|
||||||
|
'iskBalance': number;
|
||||||
|
'itemBalances': Array<ItemBalanceResponse>;
|
||||||
|
}
|
||||||
export interface CharacterResponse {
|
export interface CharacterResponse {
|
||||||
'characterId': number;
|
'characterId': number;
|
||||||
'name': string;
|
'name': string;
|
||||||
@@ -76,6 +80,10 @@ export const IskTransferResponseTypeEnum = {
|
|||||||
|
|
||||||
export type IskTransferResponseTypeEnum = typeof IskTransferResponseTypeEnum[keyof typeof IskTransferResponseTypeEnum];
|
export type IskTransferResponseTypeEnum = typeof IskTransferResponseTypeEnum[keyof typeof IskTransferResponseTypeEnum];
|
||||||
|
|
||||||
|
export interface ItemBalanceResponse {
|
||||||
|
'typeId': number;
|
||||||
|
'quantity': number;
|
||||||
|
}
|
||||||
export interface ItemTransferResponse extends TransferResponse {
|
export interface ItemTransferResponse extends TransferResponse {
|
||||||
'fromLedgerId': string;
|
'fromLedgerId': string;
|
||||||
'toLedgerId': string;
|
'toLedgerId': string;
|
||||||
@@ -635,6 +643,39 @@ export const LedgerApiAxiosParamCreator = function (configuration?: Configuratio
|
|||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} ledgerId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
findBalanceByLedgerId: async (ledgerId: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'ledgerId' is not null or undefined
|
||||||
|
assertParamExists('findBalanceByLedgerId', 'ledgerId', ledgerId)
|
||||||
|
const localVarPath = `/ledgers/{ledgerId}/balance`
|
||||||
|
.replace('{ledgerId}', encodeURIComponent(String(ledgerId)));
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
localVarHeaderParameter['Accept'] = '*/*';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} ledgerId
|
* @param {string} ledgerId
|
||||||
@@ -788,6 +829,18 @@ export const LedgerApiFp = function(configuration?: Configuration) {
|
|||||||
const localVarOperationServerBasePath = operationServerMap['LedgerApi.findAllLedgers']?.[localVarOperationServerIndex]?.url;
|
const localVarOperationServerBasePath = operationServerMap['LedgerApi.findAllLedgers']?.[localVarOperationServerIndex]?.url;
|
||||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} ledgerId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async findBalanceByLedgerId(ledgerId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<BalanceResponse>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.findBalanceByLedgerId(ledgerId, options);
|
||||||
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
|
const localVarOperationServerBasePath = operationServerMap['LedgerApi.findBalanceByLedgerId']?.[localVarOperationServerIndex]?.url;
|
||||||
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} ledgerId
|
* @param {string} ledgerId
|
||||||
@@ -861,6 +914,15 @@ export const LedgerApiFactory = function (configuration?: Configuration, basePat
|
|||||||
findAllLedgers(options?: RawAxiosRequestConfig): AxiosPromise<Array<FindAllLedgers200ResponseInner>> {
|
findAllLedgers(options?: RawAxiosRequestConfig): AxiosPromise<Array<FindAllLedgers200ResponseInner>> {
|
||||||
return localVarFp.findAllLedgers(options).then((request) => request(axios, basePath));
|
return localVarFp.findAllLedgers(options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} ledgerId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
findBalanceByLedgerId(ledgerId: string, options?: RawAxiosRequestConfig): AxiosPromise<BalanceResponse> {
|
||||||
|
return localVarFp.findBalanceByLedgerId(ledgerId, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} ledgerId
|
* @param {string} ledgerId
|
||||||
@@ -926,6 +988,16 @@ export class LedgerApi extends BaseAPI {
|
|||||||
return LedgerApiFp(this.configuration).findAllLedgers(options).then((request) => request(this.axios, this.basePath));
|
return LedgerApiFp(this.configuration).findAllLedgers(options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} ledgerId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
public findBalanceByLedgerId(ledgerId: string, options?: RawAxiosRequestConfig) {
|
||||||
|
return LedgerApiFp(this.configuration).findBalanceByLedgerId(ledgerId, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} ledgerId
|
* @param {string} ledgerId
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import {isCombined, Ledger} from "@/ledger/ledger.ts";
|
import {isCombined, Ledger} from "@/ledger/ledger.ts";
|
||||||
import {FolderOpenIcon} from '@heroicons/vue/24/outline';
|
import {FolderOpenIcon} from '@heroicons/vue/24/outline';
|
||||||
import {RouterLink} from "vue-router";
|
import {RouterLink} from "vue-router";
|
||||||
import {routeNames} from "@/routes.ts";
|
import {routeNames} from "@/routes";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
ledger: Ledger;
|
ledger: Ledger;
|
||||||
@@ -17,7 +17,7 @@ const props = defineProps<Props>();
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<FolderOpenIcon v-if="isCombined(ledger)" class="w-4 me-1" />
|
<FolderOpenIcon v-if="isCombined(ledger)" class="w-4 me-1" />
|
||||||
<div v-else class="w-4 me-1"/>
|
<div v-else class="w-4 me-1"/>
|
||||||
<RouterLink v-if="link" :to="{name: routeNames.listLedgerTransactions, params: {ledgerId: ledger.ledgerId}}">{{ ledger.name }}</RouterLink>
|
<RouterLink v-if="link" :to="{name: routeNames.viewLedger, params: {ledgerId: ledger.ledgerId}}">{{ ledger.name }}</RouterLink>
|
||||||
<span v-else>{{ ledger.name }}</span>
|
<span v-else>{{ ledger.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import {
|
|||||||
UpdateMainLedgerRequest
|
UpdateMainLedgerRequest
|
||||||
} from "@/generated/mammon";
|
} from "@/generated/mammon";
|
||||||
import {defineStore} from "pinia";
|
import {defineStore} from "pinia";
|
||||||
import {ref, triggerRef} from "vue";
|
import {computed, ref, triggerRef} from "vue";
|
||||||
import {ledgerApi, transactionApi} from "@/mammon";
|
import {ledgerApi, transactionApi} from "@/mammon";
|
||||||
|
import {useRouteParams} from "@vueuse/router";
|
||||||
|
|
||||||
export const LedgerTypes = LedgerResponseTypeEnum;
|
export const LedgerTypes = LedgerResponseTypeEnum;
|
||||||
|
|
||||||
@@ -76,3 +77,10 @@ export const useLedgersStore = defineStore('ledgers', () => {
|
|||||||
|
|
||||||
export const findAllTransactionInLeger = (ledger: Ledger | string): Promise<TransactionResponse[]> => transactionApi.finAllTransactionsInLedger(typeof ledger == 'string' ? ledger : ledger.ledgerId).then(response => response.data)
|
export const findAllTransactionInLeger = (ledger: Ledger | string): Promise<TransactionResponse[]> => transactionApi.finAllTransactionsInLedger(typeof ledger == 'string' ? ledger : ledger.ledgerId).then(response => response.data)
|
||||||
|
|
||||||
|
export const useLedgerParam = () => {
|
||||||
|
const {findById} = useLedgersStore();
|
||||||
|
const ledgerId = useRouteParams<string, string>('ledgerId', '', { transform: v => typeof v === 'string' ? v : v[0]});
|
||||||
|
const ledger = computed(() => findById(ledgerId.value))
|
||||||
|
|
||||||
|
return {ledgerId, ledger};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,29 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {ref, watch} from "vue";
|
import {findAllTransactionInLeger, TransferTypes, useLedgerParam} from "@/ledger";
|
||||||
import {useRoute} from "vue-router";
|
|
||||||
import log from "loglevel";
|
|
||||||
import {findAllTransactionInLeger, Ledger, TransferTypes, useLedgersStore} from "@/ledger";
|
|
||||||
import {computedAsync} from "@vueuse/core";
|
import {computedAsync} from "@vueuse/core";
|
||||||
import {TransactionResponse} from "@/generated/mammon";
|
import {TransactionResponse} from "@/generated/mammon";
|
||||||
import {formatEveDate} from "@/formaters.ts";
|
import {formatEveDate} from "@/formaters.ts";
|
||||||
import {IskLabel} from "@/market";
|
import {IskLabel} from "@/market";
|
||||||
|
|
||||||
const {findById, refresh} = useLedgersStore();
|
const {ledgerId, ledger} = useLedgerParam();
|
||||||
|
|
||||||
const ledger = ref<Ledger>();
|
|
||||||
|
|
||||||
const transactions = computedAsync<TransactionResponse[]>(async () => {
|
const transactions = computedAsync<TransactionResponse[]>(async () => {
|
||||||
if (ledger.value) {
|
if (ledgerId.value) {
|
||||||
return await findAllTransactionInLeger(ledger.value.ledgerId);
|
return await findAllTransactionInLeger(ledgerId.value);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getIskBalance = (transaction: TransactionResponse) => {
|
const getIskBalance = (transaction: TransactionResponse) => {
|
||||||
const ledgerId = ledger.value?.ledgerId;
|
if (!ledgerId.value) {
|
||||||
|
|
||||||
if (!ledgerId) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,29 +24,15 @@ const getIskBalance = (transaction: TransactionResponse) => {
|
|||||||
|
|
||||||
for (const transfer of transaction.transfers) {
|
for (const transfer of transaction.transfers) {
|
||||||
if (transfer.type === TransferTypes.Isk) {
|
if (transfer.type === TransferTypes.Isk) {
|
||||||
if (transfer.toLedgerId === ledgerId) {
|
if (transfer.toLedgerId === ledgerId.value) {
|
||||||
balance += transfer.amount;
|
balance += transfer.amount;
|
||||||
} else if (transfer.fromLedgerId === ledgerId) {
|
} else if (transfer.fromLedgerId === ledgerId.value) {
|
||||||
balance -= transfer.amount;
|
balance -= transfer.amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return balance;
|
return balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(useRoute(), async route => {
|
|
||||||
if (route.params.ledgerId) {
|
|
||||||
const id = typeof route.params.ledgerId === 'string' ? route.params.ledgerId : route.params.ledgerId[0];
|
|
||||||
|
|
||||||
await refresh() // FIXME
|
|
||||||
|
|
||||||
ledger.value = findById(id)
|
|
||||||
log.info('Loaded ledger:', ledger.value);
|
|
||||||
} else {
|
|
||||||
ledger.value = undefined;
|
|
||||||
log.info('No ledger to load');
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {RouterLink, RouterView} from 'vue-router';
|
||||||
|
import {isMain, useLedgerParam} from "@/ledger";
|
||||||
|
import {routeNames} from "@/routes.ts";
|
||||||
|
|
||||||
|
const {ledgerId, ledger} = useLedgerParam();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="ledger" class="mt-4">
|
||||||
|
<div class="flex border-b-2 border-emerald-500">
|
||||||
|
<RouterLink :to="{name: routeNames.viewLedgerBalance}" class="tab">
|
||||||
|
<span>Balance</span>
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink v-if="isMain(ledger)" :to="{name: routeNames.listLedgerTransactions}" class="tab">
|
||||||
|
<span>Transactions</span>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
<RouterView />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {useLedgerParam} from "@/ledger";
|
||||||
|
|
||||||
|
const {ledgerId, ledger} = useLedgerParam();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mt-4">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
+10
-4
@@ -3,7 +3,9 @@ import {RouteRecordRaw} from 'vue-router';
|
|||||||
export const routeNames = {
|
export const routeNames = {
|
||||||
home: 'home',
|
home: 'home',
|
||||||
callback: 'callback',
|
callback: 'callback',
|
||||||
listLedgerTransactions: 'list-ledger-tTransactions',
|
viewLedger: 'view-ledger',
|
||||||
|
viewLedgerBalance: 'view-ledger-balance',
|
||||||
|
listLedgerTransactions: 'list-ledger-transactions',
|
||||||
listRuleBooks: 'list-rule-books',
|
listRuleBooks: 'list-rule-books',
|
||||||
newRuleBook: 'new-rule-book',
|
newRuleBook: 'new-rule-book',
|
||||||
editRuleBook: 'edit-rule-book',
|
editRuleBook: 'edit-rule-book',
|
||||||
@@ -18,11 +20,15 @@ export const routes: RouteRecordRaw[] = [
|
|||||||
|
|
||||||
{path: '/ledgers', component: () => import('@/pages/Ledgers.vue'), children: [
|
{path: '/ledgers', component: () => import('@/pages/Ledgers.vue'), children: [
|
||||||
{path: '', component: () => import('@/pages/ledger/ListLedgers.vue')},
|
{path: '', component: () => import('@/pages/ledger/ListLedgers.vue')},
|
||||||
{path: ':ledgerId/transactions', name: routeNames.listLedgerTransactions, component: () => import('@/pages/ledger/ListLedgerTransactions.vue')},
|
{path: ':ledgerId', component: () => import('./pages/ledger/ViewLedger.vue'), children: [
|
||||||
|
{path: '', name: routeNames.viewLedger, redirect: {name: routeNames.viewLedgerBalance}},
|
||||||
|
{path: 'balance', name: routeNames.viewLedgerBalance, component: () => import('@/pages/ledger/ViewLedgerBalance.vue')},
|
||||||
|
{path: 'transactions', name: routeNames.listLedgerTransactions, component: () => import('@/pages/ledger/ListLedgerTransactions.vue')},
|
||||||
|
]},
|
||||||
]},
|
]},
|
||||||
|
|
||||||
{path: '/rules', component: () => import('@/pages/Rules.vue'), children: [
|
{path: '/rules', component: () => import('@/pages/Rules.vue'), children: [
|
||||||
{path: '', redirect: '/rule-books'},
|
{path: '', redirect: {name: routeNames.listRuleBooks}},
|
||||||
{path: '/rule-books', children: [
|
{path: '/rule-books', children: [
|
||||||
{path: '', name: routeNames.listRuleBooks, component: () => import('@/pages/rules/ListRuleBooks.vue')},
|
{path: '', name: routeNames.listRuleBooks, component: () => import('@/pages/rules/ListRuleBooks.vue')},
|
||||||
{path: 'new', name: routeNames.newRuleBook, component: () => import('@/pages/rules/EditRuleBook.vue')},
|
{path: 'new', name: routeNames.newRuleBook, component: () => import('@/pages/rules/EditRuleBook.vue')},
|
||||||
@@ -35,7 +41,7 @@ export const routes: RouteRecordRaw[] = [
|
|||||||
]},
|
]},
|
||||||
|
|
||||||
{path: '/market', component: () => import('@/pages/Market.vue'), children: [
|
{path: '/market', component: () => import('@/pages/Market.vue'), children: [
|
||||||
{path: '', redirect: '/market/types'},
|
{path: '', redirect: {name: routeNames.marketTypes}},
|
||||||
{path: 'types/:type?', name: routeNames.marketTypes, component: () => import('@/pages/market/TypeInfo.vue')},
|
{path: 'types/:type?', name: routeNames.marketTypes, component: () => import('@/pages/market/TypeInfo.vue')},
|
||||||
{path: 'tracking', component: () => import('@/pages/market/Tracking.vue')},
|
{path: 'tracking', component: () => import('@/pages/market/Tracking.vue')},
|
||||||
{path: 'acquisitions', component: () => import('@/pages/market/Acquisitions.vue')},
|
{path: 'acquisitions', component: () => import('@/pages/market/Acquisitions.vue')},
|
||||||
|
|||||||
Reference in New Issue
Block a user