Update .gitignore, add dist directory
This commit is contained in:
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
import type { BunMariaDBConfig, DBResponseObject, ServerQueryParam } from "../../types";
|
||||
type Params<Schema extends {
|
||||
[k: string]: any;
|
||||
} = {
|
||||
[k: string]: any;
|
||||
}, Table extends string = string> = {
|
||||
table: Table;
|
||||
query?: ServerQueryParam<Schema>;
|
||||
targetId?: number | string;
|
||||
config?: BunMariaDBConfig;
|
||||
};
|
||||
export default function DbDelete<Schema extends {
|
||||
[k: string]: any;
|
||||
} = {
|
||||
[k: string]: any;
|
||||
}, Table extends string = string>({ table, query, targetId, config, }: Params<Schema, Table>): Promise<DBResponseObject>;
|
||||
export {};
|
||||
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
import dbHandler from "../db-handler";
|
||||
import _ from "lodash";
|
||||
import sqlGenerator from "../../utils/sql-generator";
|
||||
function quoteIdentifier(identifier) {
|
||||
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||
}
|
||||
export default async function DbDelete({ table, query, targetId, config, }) {
|
||||
let sqlObj = null;
|
||||
try {
|
||||
let finalQuery = query || {};
|
||||
if (targetId) {
|
||||
finalQuery = _.merge(finalQuery, {
|
||||
query: {
|
||||
id: {
|
||||
value: String(targetId),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
sqlObj = sqlGenerator({
|
||||
tableName: quoteIdentifier(table),
|
||||
genObject: finalQuery,
|
||||
});
|
||||
const whereClause = sqlObj.string.match(/WHERE .*/)?.[0];
|
||||
if (!whereClause) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `No WHERE clause`,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
let sql = `DELETE FROM ${quoteIdentifier(table)} ${whereClause}`;
|
||||
sql += ` RETURNING *`;
|
||||
sqlObj.string = sql;
|
||||
const res = await dbHandler({
|
||||
query: sql,
|
||||
values: sqlObj.values,
|
||||
config,
|
||||
});
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database delete failed",
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
..._.omit(res, ["payload"]),
|
||||
success: Boolean(res.insert_return?.last_insert_id),
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import type { BUN_MARIADB_TableSchemaType } from "../../types";
|
||||
type Param = {
|
||||
paradigm: "JavaScript" | "TypeScript" | undefined;
|
||||
table: BUN_MARIADB_TableSchemaType;
|
||||
query?: any;
|
||||
typeDefName?: string;
|
||||
allValuesOptional?: boolean;
|
||||
addExport?: boolean;
|
||||
dbName?: string;
|
||||
};
|
||||
export default function generateTypeDefinition({ paradigm, table, query, typeDefName, allValuesOptional, addExport, dbName, }: Param): {
|
||||
typeDefinition: string | null;
|
||||
tdName: string;
|
||||
};
|
||||
export {};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
export default function generateTypeDefinition({ paradigm, table, query, typeDefName, allValuesOptional, addExport, dbName, }) {
|
||||
let typeDefinition = ``;
|
||||
let tdName = ``;
|
||||
try {
|
||||
tdName = typeDefName
|
||||
? typeDefName
|
||||
: dbName
|
||||
? `BUN_MARIADB_${dbName}_${table.tableName}`.toUpperCase()
|
||||
: `BUN_MARIADB_${query.single}_${query.single_table}`.toUpperCase();
|
||||
const fields = table.fields;
|
||||
function typeMap(schemaType) {
|
||||
if (schemaType.options && schemaType.options.length > 0) {
|
||||
let opts = schemaType.options.map((opt) => schemaType.dataType?.match(/int/i) || typeof opt == "number"
|
||||
? `${opt}`
|
||||
: `"${opt}"`);
|
||||
opts.push(`""`);
|
||||
return opts.join(" | ");
|
||||
}
|
||||
if (schemaType.dataType?.match(/blob/i)) {
|
||||
return `Float32Array<ArrayBuffer> | Buffer<ArrayBuffer> | null`;
|
||||
}
|
||||
if (schemaType.dataType?.match(/int|double|decimal|real/i)) {
|
||||
return `number | ""`;
|
||||
}
|
||||
if (schemaType.dataType?.match(/text|varchar|timestamp/i)) {
|
||||
return `string`;
|
||||
}
|
||||
if (schemaType.dataType?.match(/boolean/i)) {
|
||||
return "0 | 1";
|
||||
}
|
||||
return "string";
|
||||
}
|
||||
const typesArrayTypeScript = [];
|
||||
const typesArrayJavascript = [];
|
||||
typesArrayTypeScript.push(`${addExport ? "export " : ""}type ${tdName} = {`);
|
||||
typesArrayJavascript.push(`/**\n * @typedef {object} ${tdName}`);
|
||||
fields.forEach((field) => {
|
||||
if (field.fieldDescription) {
|
||||
typesArrayTypeScript.push(` /** \n * ${field.fieldDescription}\n */`);
|
||||
}
|
||||
const nullValue = allValuesOptional
|
||||
? "?"
|
||||
: field.notNullValue
|
||||
? ""
|
||||
: "?";
|
||||
typesArrayTypeScript.push(` ${field.fieldName}${nullValue}: ${typeMap(field)};`);
|
||||
typesArrayJavascript.push(` * @property {${typeMap(field)}${nullValue}} ${field.fieldName}`);
|
||||
});
|
||||
typesArrayTypeScript.push(`}`);
|
||||
typesArrayJavascript.push(` */`);
|
||||
if (paradigm?.match(/javascript/i)) {
|
||||
typeDefinition = typesArrayJavascript.join("\n");
|
||||
}
|
||||
if (paradigm?.match(/typescript/i)) {
|
||||
typeDefinition = typesArrayTypeScript.join("\n");
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error.message);
|
||||
typeDefinition = null;
|
||||
}
|
||||
return { typeDefinition, tdName };
|
||||
}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
import type { BunMariaDBConfig, DBResponseObject } from "../../types";
|
||||
type Params<Schema extends {
|
||||
[k: string]: any;
|
||||
} = {
|
||||
[k: string]: any;
|
||||
}, Table extends string = string> = {
|
||||
table: Table;
|
||||
data: Schema[];
|
||||
update_on_duplicate?: boolean;
|
||||
config?: BunMariaDBConfig;
|
||||
};
|
||||
export default function DbInsert<Schema extends {
|
||||
[k: string]: any;
|
||||
} = {
|
||||
[k: string]: any;
|
||||
}, Table extends string = string>({ table, data, update_on_duplicate, config, }: Params<Schema, Table>): Promise<DBResponseObject>;
|
||||
export {};
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
import dbHandler from "../db-handler";
|
||||
import sqlInsertGenerator from "../../utils/sql-insert-generator";
|
||||
import { sanitizeHtmlFieldsBatch } from "../../utils/sanitize-html-fields";
|
||||
import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql";
|
||||
export default async function DbInsert({ table, data, update_on_duplicate, config, }) {
|
||||
let sqlObj = null;
|
||||
try {
|
||||
const sanitizedData = sanitizeHtmlFieldsBatch({
|
||||
table,
|
||||
data,
|
||||
config,
|
||||
});
|
||||
const finalData = sanitizedData.map((d) => ({
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now(),
|
||||
...d,
|
||||
}));
|
||||
sqlObj =
|
||||
sqlInsertGenerator({
|
||||
tableName: table,
|
||||
data: finalData,
|
||||
}) || null;
|
||||
if (!sqlObj) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "No insert SQL generated",
|
||||
};
|
||||
}
|
||||
let sql = sqlObj.query;
|
||||
if (update_on_duplicate && data[0]) {
|
||||
sql = await grabDuplicateSafeInsertSql({ data, table, sql });
|
||||
}
|
||||
sql += ` RETURNING *`;
|
||||
const res = await dbHandler({
|
||||
query: sql,
|
||||
values: sqlObj.values,
|
||||
config,
|
||||
});
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database insert failed",
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...res,
|
||||
success: Boolean(res?.insert_return?.affected_rows ||
|
||||
res.insert_return?.last_insert_id),
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import type { BUN_MARIADB_DatabaseSchemaType, BunMariaDBConfig } from "../../types";
|
||||
type Params = {
|
||||
dbSchema: BUN_MARIADB_DatabaseSchemaType;
|
||||
config: BunMariaDBConfig;
|
||||
};
|
||||
export default function dbSchemaToType({ config, dbSchema, }: Params): string[] | undefined;
|
||||
export {};
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import _ from "lodash";
|
||||
import generateTypeDefinition from "./db-generate-type-defs";
|
||||
export default function dbSchemaToType({ config, dbSchema, }) {
|
||||
let datasquirelSchema = dbSchema;
|
||||
if (!datasquirelSchema)
|
||||
return;
|
||||
let tableNames = `export const BunMariaDBTables = [\n${datasquirelSchema.tables
|
||||
.map((tbl) => ` "${tbl.tableName}",`)
|
||||
.join("\n")}\n] as const`;
|
||||
const dbTablesSchemas = datasquirelSchema.tables;
|
||||
const defDbName = config.db_name
|
||||
?.toUpperCase()
|
||||
.replace(/[^a-zA-Z0-9]/g, "_");
|
||||
const defNames = [];
|
||||
const schemas = dbTablesSchemas
|
||||
.map((table) => {
|
||||
let final_table = _.cloneDeep(table);
|
||||
if (final_table.parentTableName) {
|
||||
const parent_table = dbTablesSchemas.find((t) => t.tableName === final_table.parentTableName);
|
||||
if (parent_table) {
|
||||
final_table = _.merge(parent_table, {
|
||||
tableName: final_table.tableName,
|
||||
tableDescription: final_table.tableDescription,
|
||||
});
|
||||
}
|
||||
}
|
||||
const defObj = generateTypeDefinition({
|
||||
paradigm: "TypeScript",
|
||||
table: final_table,
|
||||
typeDefName: `BUN_MARIADB_${defDbName}_${final_table.tableName.toUpperCase()}`,
|
||||
allValuesOptional: true,
|
||||
addExport: true,
|
||||
});
|
||||
if (defObj.tdName?.match(/./)) {
|
||||
defNames.push(defObj.tdName);
|
||||
}
|
||||
return defObj.typeDefinition;
|
||||
})
|
||||
.filter((schm) => typeof schm == "string");
|
||||
const allTd = defNames?.[0]
|
||||
? `export type BUN_MARIADB_${defDbName}_ALL_TYPEDEFS = ${defNames.join(` & `)}`
|
||||
: ``;
|
||||
return [tableNames, ...schemas, allTd];
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
import type { BunMariaDBConfig, DBResponseObject, ServerQueryParam } from "../../types";
|
||||
type Params<Schema extends {
|
||||
[k: string]: any;
|
||||
} = {
|
||||
[k: string]: any;
|
||||
}, Table extends string = string> = {
|
||||
query?: ServerQueryParam<Schema>;
|
||||
table: Table;
|
||||
count?: boolean;
|
||||
targetId?: number | string;
|
||||
config?: BunMariaDBConfig;
|
||||
};
|
||||
export default function DbSelect<Schema extends {
|
||||
[k: string]: any;
|
||||
} = {
|
||||
[k: string]: any;
|
||||
}, Table extends string = string>({ table, query, count, targetId, config, }: Params<Schema, Table>): Promise<DBResponseObject<Schema>>;
|
||||
export {};
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
import dbHandler from "../db-handler";
|
||||
import _ from "lodash";
|
||||
import sqlGenerator from "../../utils/sql-generator";
|
||||
function quoteIdentifier(identifier) {
|
||||
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||
}
|
||||
export default async function DbSelect({ table, query, count, targetId, config, }) {
|
||||
let sqlObj = null;
|
||||
try {
|
||||
let finalQuery = query || {};
|
||||
if (targetId) {
|
||||
finalQuery = _.merge(finalQuery, {
|
||||
query: {
|
||||
id: {
|
||||
value: String(targetId),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
sqlObj = sqlGenerator({
|
||||
tableName: quoteIdentifier(table),
|
||||
genObject: finalQuery,
|
||||
});
|
||||
const res = await dbHandler({
|
||||
query: sqlObj.string,
|
||||
values: sqlObj.values,
|
||||
config,
|
||||
});
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database select failed",
|
||||
debug: {
|
||||
sqlObj,
|
||||
sql: sqlObj.string,
|
||||
},
|
||||
};
|
||||
}
|
||||
const batchRes = (res.payload || []);
|
||||
let resp = {
|
||||
success: true,
|
||||
payload: batchRes,
|
||||
single_res: batchRes[0],
|
||||
debug: {
|
||||
sqlObj,
|
||||
sql: sqlObj.string,
|
||||
},
|
||||
};
|
||||
if (count) {
|
||||
const countSqlObject = sqlGenerator({
|
||||
tableName: quoteIdentifier(table),
|
||||
genObject: finalQuery,
|
||||
count,
|
||||
});
|
||||
const countSql = `SELECT COUNT(*) AS count FROM (${countSqlObject.string}) AS c`;
|
||||
const countRes = await dbHandler({
|
||||
query: countSql,
|
||||
values: countSqlObject.values,
|
||||
config,
|
||||
});
|
||||
if (!countRes.success) {
|
||||
return {
|
||||
...resp,
|
||||
success: false,
|
||||
msg: "Database count failed",
|
||||
debug: {
|
||||
...resp.debug,
|
||||
count_sql: countSql,
|
||||
},
|
||||
};
|
||||
}
|
||||
const countRows = countRes.payload || [];
|
||||
const countVal = countRows[0]?.count ?? countRows[0]?.["count"];
|
||||
resp = {
|
||||
...resp,
|
||||
count: Number(countVal),
|
||||
debug: {
|
||||
...resp.debug,
|
||||
count_sql: countSql,
|
||||
},
|
||||
};
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
import type { DBResponseObject, SQLInsertGenValueType } from "../../types";
|
||||
type Params = {
|
||||
sql: string;
|
||||
values?: SQLInsertGenValueType[];
|
||||
};
|
||||
export default function DbSQL<T extends {
|
||||
[k: string]: any;
|
||||
} = {
|
||||
[k: string]: any;
|
||||
}>({ sql, values }: Params): Promise<DBResponseObject<T>>;
|
||||
export {};
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
import dbHandler from "../db-handler";
|
||||
export default async function DbSQL({ sql, values }) {
|
||||
try {
|
||||
const trimmedSql = sql.trim();
|
||||
const isSelect = trimmedSql.match(/^select/i);
|
||||
const res = await dbHandler({
|
||||
query: trimmedSql,
|
||||
values: values,
|
||||
});
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database query failed",
|
||||
debug: {
|
||||
sqlObj: {
|
||||
sql: trimmedSql,
|
||||
values,
|
||||
},
|
||||
sql,
|
||||
},
|
||||
};
|
||||
}
|
||||
const payload = isSelect ? (res.payload || []) : undefined;
|
||||
const single_res = isSelect ? payload?.[0] : res.single_res;
|
||||
const singleRaw = res.single_res;
|
||||
return {
|
||||
success: true,
|
||||
payload,
|
||||
single_res,
|
||||
debug: {
|
||||
sqlObj: {
|
||||
sql: trimmedSql,
|
||||
values,
|
||||
},
|
||||
sql,
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
import type { BunMariaDBConfig, DBResponseObject, ServerQueryParam } from "../../types";
|
||||
type Params<Schema extends {
|
||||
[k: string]: any;
|
||||
} = {
|
||||
[k: string]: any;
|
||||
}, Table extends string = string> = {
|
||||
table: Table;
|
||||
data: Schema;
|
||||
query?: ServerQueryParam<Schema>;
|
||||
targetId?: number | string;
|
||||
config?: BunMariaDBConfig;
|
||||
};
|
||||
export default function DbUpdate<Schema extends {
|
||||
[k: string]: any;
|
||||
} = {
|
||||
[k: string]: any;
|
||||
}, Table extends string = string>({ table, data, query, targetId, config, }: Params<Schema, Table>): Promise<DBResponseObject>;
|
||||
export {};
|
||||
Vendored
+109
@@ -0,0 +1,109 @@
|
||||
import dbHandler from "../db-handler";
|
||||
import _ from "lodash";
|
||||
import sqlGenerator from "../../utils/sql-generator";
|
||||
import sanitizeHtmlFields from "../../utils/sanitize-html-fields";
|
||||
function quoteIdentifier(identifier) {
|
||||
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||
}
|
||||
export default async function DbUpdate({ table, data, query, targetId, config, }) {
|
||||
let sqlObj = { string: "", values: [] };
|
||||
try {
|
||||
let finalQuery = query || {};
|
||||
if (targetId) {
|
||||
finalQuery = _.merge(finalQuery, {
|
||||
query: {
|
||||
id: {
|
||||
value: String(targetId),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
const sqlQueryObj = sqlGenerator({
|
||||
tableName: quoteIdentifier(table),
|
||||
genObject: finalQuery,
|
||||
});
|
||||
const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0];
|
||||
if (!whereClause) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `No WHERE clause`,
|
||||
};
|
||||
}
|
||||
let values = [];
|
||||
let sql = ``;
|
||||
sql += `UPDATE ${quoteIdentifier(table)} SET`;
|
||||
const sanitizedData = sanitizeHtmlFields({
|
||||
table,
|
||||
data,
|
||||
config,
|
||||
});
|
||||
const finalData = {
|
||||
updated_at: Date.now(),
|
||||
...sanitizedData,
|
||||
};
|
||||
const keys = Object.keys(finalData);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (!key)
|
||||
continue;
|
||||
const isLast = i == keys.length - 1;
|
||||
sql += ` ${quoteIdentifier(key)}=?`;
|
||||
values.push(finalData[key] ?? null);
|
||||
if (!isLast) {
|
||||
sql += `,`;
|
||||
}
|
||||
}
|
||||
sql += ` ${whereClause}`;
|
||||
values = [...values, ...sqlQueryObj.values];
|
||||
const res = await dbHandler({
|
||||
query: sql,
|
||||
values: values,
|
||||
config,
|
||||
});
|
||||
sqlObj.string = sql;
|
||||
sqlObj.values = values;
|
||||
let updated_sql = ``;
|
||||
let updated_sql_values = [];
|
||||
updated_sql += `SELECT * FROM ${quoteIdentifier(table)} ${whereClause}`;
|
||||
updated_sql_values = [...updated_sql_values, ...sqlQueryObj.values];
|
||||
updated_sql += ` AND `;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (!key)
|
||||
continue;
|
||||
if (key == "updated_at")
|
||||
continue;
|
||||
const isLast = i == keys.length - 1;
|
||||
updated_sql += ` ${quoteIdentifier(key)}=?`;
|
||||
updated_sql_values.push(finalData[key] ?? null);
|
||||
if (!isLast) {
|
||||
updated_sql += ` AND `;
|
||||
}
|
||||
}
|
||||
const updated_res = await dbHandler({
|
||||
query: updated_sql,
|
||||
values: updated_sql_values,
|
||||
config,
|
||||
});
|
||||
const affected_rows = updated_res.payload?.length;
|
||||
return {
|
||||
...res,
|
||||
success: Boolean(affected_rows),
|
||||
insert_return: {
|
||||
affected_rows,
|
||||
},
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import type { BUN_MARIADB_DatabaseSchemaType, BunMariaDBConfig } from "../../types";
|
||||
type Params = {
|
||||
dbSchema: BUN_MARIADB_DatabaseSchemaType;
|
||||
dst_file: string;
|
||||
config: BunMariaDBConfig;
|
||||
};
|
||||
export default function dbSchemaToTypeDef({ dbSchema, dst_file, config, }: Params): void;
|
||||
export {};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import path from "node:path";
|
||||
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import dbSchemaToType from "./db-schema-to-typedef";
|
||||
export default function dbSchemaToTypeDef({ dbSchema, dst_file, config, }) {
|
||||
try {
|
||||
if (!dbSchema)
|
||||
throw new Error("No schema found");
|
||||
const definitions = dbSchemaToType({ dbSchema, config });
|
||||
const ourfileDir = path.dirname(dst_file);
|
||||
if (!existsSync(ourfileDir)) {
|
||||
mkdirSync(ourfileDir, { recursive: true });
|
||||
}
|
||||
writeFileSync(dst_file, definitions?.join("\n\n") || "", "utf-8");
|
||||
}
|
||||
catch (error) {
|
||||
console.log(`Schema to Typedef Error =>`, error.message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user