chore: format

This commit is contained in:
r1oga
2022-10-20 13:24:10 +02:00
parent cb2b6f6923
commit 8aaa70c560
56 changed files with 9871 additions and 9951 deletions

View File

@@ -1,92 +1,92 @@
{
"name": "autism-node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev-server": "nodemon ./build/server.js",
"watch-server": "webpack --config webpack.server.config.js --watch",
"build-server": "webpack --config webpack.server.config.js",
"dev": "NODE_ENV=development concurrently --kill-others-on-fail npm:watch-server npm:dev-server",
"build": "NODE_ENV=production npm run build-server",
"start": "NODE_ENV=production npm run build && node ./build/server.js",
"test": "NODE_ENV=test TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' tape -r ts-node/register ./src/**/*.test.ts ./src/**/**/*.test.ts | tap-spec",
"test:coverage": "nyc npm run test",
"format:check": "prettier --check 'src/**/*.ts'",
"format:write": "prettier --write 'src/**/*.ts'",
"prepare": "husky install"
},
"author": "",
"license": "ISC",
"dependencies": {
"@ensdomains/ensjs": "^2.0.1",
"@interep/reputation": "^0.4.0",
"@zk-kit/identity": "^1.4.1",
"@zk-kit/protocols": "^1.11.1",
"async-mutex": "^0.3.1",
"bn.js": "^5.2.0",
"body-parser": "^1.19.0",
"botometer": "^1.0.12",
"connect-session-sequelize": "^7.1.2",
"cors": "^2.8.5",
"dotenv": "^16.0.1",
"elliptic": "^6.5.4",
"ethereumjs-util": "^7.1.2",
"express": "^4.17.1",
"express-fileupload": "^1.4.0",
"express-session": "^1.17.2",
"gun": "0.2020.1232",
"http-terminator": "^3.2.0",
"ipfs-http-client": "56.x",
"isomorphic-fetch": "^3.0.0",
"jsonwebtoken": "^8.5.1",
"link-preview-js": "^2.1.8",
"link-preview-node": "^1.0.7",
"lru-cache": "^6.0.0",
"multer": "^1.4.5-lts.1",
"oauth-1.0a": "^2.2.6",
"pg": "^8.7.1",
"semaphore-lib": "git+https://github.com/akinovak/semaphore-lib.git#dev",
"sequelize": "6.6.5",
"sqlite3": "^5.0.2",
"web3": "^1.3.5",
"web3.storage": "^4.3.0",
"winston": "^3.3.3",
"zk-chat-server": "^1.0.4"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/cors": "^2.8.10",
"@types/elliptic": "^6.4.14",
"@types/express": "^4.17.11",
"@types/express-fileupload": "^1.2.2",
"@types/express-session": "^1.17.4",
"@types/jsonwebtoken": "^8.5.6",
"@types/multer": "^1.4.7",
"@types/node": "^14.14.37",
"@types/sinon": "^10.0.11",
"@types/tape": "^4.13.2",
"concurrently": "^6.2.0",
"csv": "^6.0.5",
"husky": "^8.0.0",
"node-loader": "^2.0.0",
"nodemon": "^2.0.12",
"nyc": "^15.1.0",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3",
"sinon": "^13.0.1",
"source-map-support": "^0.5.21",
"tap-diff": "^0.1.1",
"tap-spec": "^5.0.0",
"tape": "^5.5.2",
"ts-loader": "^6.2.1",
"ts-node": "^10.7.0",
"typescript": "^4.3.5",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-node-externals": "^3.0.0"
},
"engines": {
"node": ">=16 <17"
}
"name": "autism-node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev-server": "nodemon ./build/server.js",
"watch-server": "webpack --config webpack.server.config.js --watch",
"build-server": "webpack --config webpack.server.config.js",
"dev": "NODE_ENV=development concurrently --kill-others-on-fail npm:watch-server npm:dev-server",
"build": "NODE_ENV=production npm run build-server",
"start": "NODE_ENV=production npm run build && node ./build/server.js",
"test": "NODE_ENV=test TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' tape -r ts-node/register ./src/**/*.test.ts ./src/**/**/*.test.ts | tap-spec",
"test:coverage": "nyc npm run test",
"format:check": "prettier --check 'src/**/*.ts'",
"format:write": "prettier --write 'src/**/*.ts'",
"prepare": "husky install"
},
"author": "",
"license": "ISC",
"dependencies": {
"@ensdomains/ensjs": "^2.0.1",
"@interep/reputation": "^0.4.0",
"@zk-kit/identity": "^1.4.1",
"@zk-kit/protocols": "^1.11.1",
"async-mutex": "^0.3.1",
"bn.js": "^5.2.0",
"body-parser": "^1.19.0",
"botometer": "^1.0.12",
"connect-session-sequelize": "^7.1.2",
"cors": "^2.8.5",
"dotenv": "^16.0.1",
"elliptic": "^6.5.4",
"ethereumjs-util": "^7.1.2",
"express": "^4.17.1",
"express-fileupload": "^1.4.0",
"express-session": "^1.17.2",
"gun": "0.2020.1232",
"http-terminator": "^3.2.0",
"ipfs-http-client": "56.x",
"isomorphic-fetch": "^3.0.0",
"jsonwebtoken": "^8.5.1",
"link-preview-js": "^2.1.8",
"link-preview-node": "^1.0.7",
"lru-cache": "^6.0.0",
"multer": "^1.4.5-lts.1",
"oauth-1.0a": "^2.2.6",
"pg": "^8.7.1",
"semaphore-lib": "git+https://github.com/akinovak/semaphore-lib.git#dev",
"sequelize": "6.6.5",
"sqlite3": "^5.0.2",
"web3": "^1.3.5",
"web3.storage": "^4.3.0",
"winston": "^3.3.3",
"zk-chat-server": "^1.0.4"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/cors": "^2.8.10",
"@types/elliptic": "^6.4.14",
"@types/express": "^4.17.11",
"@types/express-fileupload": "^1.2.2",
"@types/express-session": "^1.17.4",
"@types/jsonwebtoken": "^8.5.6",
"@types/multer": "^1.4.7",
"@types/node": "^14.14.37",
"@types/sinon": "^10.0.11",
"@types/tape": "^4.13.2",
"concurrently": "^6.2.0",
"csv": "^6.0.5",
"husky": "^8.0.0",
"node-loader": "^2.0.0",
"nodemon": "^2.0.12",
"nyc": "^15.1.0",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3",
"sinon": "^13.0.1",
"source-map-support": "^0.5.21",
"tap-diff": "^0.1.1",
"tap-spec": "^5.0.0",
"tape": "^5.5.2",
"ts-loader": "^6.2.1",
"ts-node": "^10.7.0",
"typescript": "^4.3.5",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-node-externals": "^3.0.0"
},
"engines": {
"node": ">=16 <17"
}
}

View File

@@ -1,8 +1,8 @@
module.exports = {
trailingComma: 'es5',
semi: true,
singleQuote: true,
printWidth: 100,
arrowParens: 'avoid',
tabWidth: 4,
trailingComma: 'es5',
semi: true,
singleQuote: true,
printWidth: 100,
arrowParens: 'avoid',
tabWidth: 2,
};

View File

@@ -13,21 +13,21 @@ import MerkleService from './services/merkle';
import { ReputationService } from './services/reputation';
(async function initApp() {
try {
const main = new MainService();
main.add('db', new DBService());
main.add('merkle', new MerkleService());
main.add('interrep', new InterrepService());
main.add('zkchat', new ZKChatService());
main.add('ens', new ENSService());
main.add('arbitrum', new ArbitrumService());
main.add('gun', new GunService());
main.add('ipfs', new IPFSService());
main.add('http', new HttpService());
main.add('reputation', new ReputationService());
await main.start();
} catch (e) {
logger.error(e.message, { stack: e.stack });
throw e;
}
try {
const main = new MainService();
main.add('db', new DBService());
main.add('merkle', new MerkleService());
main.add('interrep', new InterrepService());
main.add('zkchat', new ZKChatService());
main.add('ens', new ENSService());
main.add('arbitrum', new ArbitrumService());
main.add('gun', new GunService());
main.add('ipfs', new IPFSService());
main.add('http', new HttpService());
main.add('reputation', new ReputationService());
await main.start();
} catch (e) {
logger.error(e.message, { stack: e.stack });
throw e;
}
})();

View File

@@ -4,111 +4,111 @@ import { Mutex } from 'async-mutex';
const mutex = new Mutex();
type AppModel = {
lastENSBlockScanned: number;
lastInterrepBlockScanned: number;
lastArbitrumBlockScanned: number;
lastGroup42BlockScanned: number;
lastENSBlockScanned: number;
lastInterrepBlockScanned: number;
lastArbitrumBlockScanned: number;
lastGroup42BlockScanned: number;
};
const app = (sequelize: Sequelize) => {
const model = sequelize.define(
'app',
{
lastENSBlockScanned: {
type: BIGINT,
},
lastInterrepBlockScanned: {
type: BIGINT,
},
lastArbitrumBlockScanned: {
type: BIGINT,
},
lastGroup42BlockScanned: {
type: BIGINT,
},
},
{}
);
const model = sequelize.define(
'app',
{
lastENSBlockScanned: {
type: BIGINT,
},
lastInterrepBlockScanned: {
type: BIGINT,
},
lastArbitrumBlockScanned: {
type: BIGINT,
},
lastGroup42BlockScanned: {
type: BIGINT,
},
},
{}
);
const read = async (): Promise<AppModel> => {
return mutex.runExclusive(async () => {
let result = await model.findOne();
return result?.toJSON() as AppModel;
const read = async (): Promise<AppModel> => {
return mutex.runExclusive(async () => {
let result = await model.findOne();
return result?.toJSON() as AppModel;
});
};
const updateLastENSBlock = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
if (result) {
return result.update({
lastENSBlockScanned: blockHeight,
});
};
}
const updateLastENSBlock = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
return model.create({
lastENSBlockScanned: blockHeight,
});
});
};
if (result) {
return result.update({
lastENSBlockScanned: blockHeight,
});
}
const updateLastInterrepBlock = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
return model.create({
lastENSBlockScanned: blockHeight,
});
if (result) {
return result.update({
lastInterrepBlockScanned: blockHeight,
});
};
}
const updateLastInterrepBlock = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
return model.create({
lastInterrepBlockScanned: blockHeight,
});
});
};
if (result) {
return result.update({
lastInterrepBlockScanned: blockHeight,
});
}
const updateLastArbitrumBlock = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
return model.create({
lastInterrepBlockScanned: blockHeight,
});
if (result) {
return result.update({
lastArbitrumBlockScanned: blockHeight,
});
};
}
const updateLastArbitrumBlock = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
return model.create({
lastArbitrumBlockScanned: blockHeight,
});
});
};
if (result) {
return result.update({
lastArbitrumBlockScanned: blockHeight,
});
}
const updateLastGroup42BlockScanned = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
return model.create({
lastArbitrumBlockScanned: blockHeight,
});
if (result) {
return result.update({
lastGroup42BlockScanned: blockHeight,
});
};
}
const updateLastGroup42BlockScanned = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
return model.create({
lastGroup42BlockScanned: blockHeight,
});
});
};
if (result) {
return result.update({
lastGroup42BlockScanned: blockHeight,
});
}
return model.create({
lastGroup42BlockScanned: blockHeight,
});
});
};
return {
model,
read,
updateLastENSBlock,
updateLastInterrepBlock,
updateLastArbitrumBlock,
updateLastGroup42BlockScanned,
};
return {
model,
read,
updateLastENSBlock,
updateLastInterrepBlock,
updateLastArbitrumBlock,
updateLastGroup42BlockScanned,
};
};
export default app;

View File

@@ -2,126 +2,126 @@ import { BIGINT, ModelCtor, Sequelize, STRING } from 'sequelize';
import { ConnectionMessageSubType } from '../util/message';
type ConnectionModel = {
messageId: string;
hash: string;
creator: string;
type: string;
subtype: string;
createdAt: number;
name: string;
messageId: string;
hash: string;
creator: string;
type: string;
subtype: string;
createdAt: number;
name: string;
};
const connections = (sequelize: Sequelize) => {
const model = sequelize.define(
'connections',
{
messageId: {
type: STRING,
allowNull: false,
},
hash: {
type: STRING,
allowNull: false,
primaryKey: true,
},
creator: {
type: STRING,
allowNull: false,
},
type: {
type: STRING,
allowNull: false,
},
subtype: {
type: STRING,
},
createdAt: {
type: BIGINT,
allowNull: false,
},
name: {
type: STRING,
},
},
{
indexes: [
{ fields: ['creator'] },
{ fields: ['subtype'] },
{ fields: ['name'] },
{ fields: ['hash'], unique: true },
{ fields: ['messageId'], unique: true },
],
}
);
const model = sequelize.define(
'connections',
{
messageId: {
type: STRING,
allowNull: false,
},
hash: {
type: STRING,
allowNull: false,
primaryKey: true,
},
creator: {
type: STRING,
allowNull: false,
},
type: {
type: STRING,
allowNull: false,
},
subtype: {
type: STRING,
},
createdAt: {
type: BIGINT,
allowNull: false,
},
name: {
type: STRING,
},
},
{
indexes: [
{ fields: ['creator'] },
{ fields: ['subtype'] },
{ fields: ['name'] },
{ fields: ['hash'], unique: true },
{ fields: ['messageId'], unique: true },
],
}
);
const findOne = async (hash: string): Promise<ConnectionModel | null> => {
let result: any = await model.findOne({
where: {
hash,
},
});
const findOne = async (hash: string): Promise<ConnectionModel | null> => {
let result: any = await model.findOne({
where: {
hash,
},
});
if (!result) return null;
if (!result) return null;
const json = result.toJSON() as ConnectionModel;
const json = result.toJSON() as ConnectionModel;
return json;
};
return json;
};
const remove = async (hash: string) => {
return model.destroy({
where: {
hash,
},
});
};
const remove = async (hash: string) => {
return model.destroy({
where: {
hash,
},
});
};
const findAllByTargetName = async (
name: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<ConnectionModel[]> => {
let result = await model.findAll({
where: {
name,
},
offset,
limit,
order: [['createdAt', order]],
});
const findAllByTargetName = async (
name: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<ConnectionModel[]> => {
let result = await model.findAll({
where: {
name,
},
offset,
limit,
order: [['createdAt', order]],
});
return result.map((r: any) => r.toJSON() as ConnectionModel);
};
return result.map((r: any) => r.toJSON() as ConnectionModel);
};
const createConnection = async (record: ConnectionModel) => {
return model.create(record);
};
const createConnection = async (record: ConnectionModel) => {
return model.create(record);
};
const findMemberInvite = async (groupAddress: string, memberAddress: string) => {
let result: any = await model.findOne({
where: {
creator: groupAddress,
name: memberAddress,
subtype: ConnectionMessageSubType.MemberInvite,
},
});
const findMemberInvite = async (groupAddress: string, memberAddress: string) => {
let result: any = await model.findOne({
where: {
creator: groupAddress,
name: memberAddress,
subtype: ConnectionMessageSubType.MemberInvite,
},
});
if (!result) return null;
if (!result) return null;
const json = result.toJSON() as ConnectionModel;
const json = result.toJSON() as ConnectionModel;
return json;
};
return json;
};
return {
model,
findOne,
remove,
findAllByTargetName,
createConnection,
findMemberInvite,
};
return {
model,
findOne,
remove,
findAllByTargetName,
createConnection,
findMemberInvite,
};
};
export default connections;

View File

@@ -1,74 +1,74 @@
import { Sequelize, BIGINT, STRING } from 'sequelize';
type ENSModel = {
ens: string;
address: string;
ens: string;
address: string;
};
const ens = (sequelize: Sequelize) => {
const model = sequelize.define(
'ens',
{
ens: {
type: STRING,
allowNull: false,
validate: {
notEmpty: true,
},
primaryKey: true,
unique: true,
},
address: {
type: STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
const model = sequelize.define(
'ens',
{
ens: {
type: STRING,
allowNull: false,
validate: {
notEmpty: true,
},
{
indexes: [{ fields: ['ens'] }, { fields: ['address'] }],
}
);
primaryKey: true,
unique: true,
},
address: {
type: STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
},
{
indexes: [{ fields: ['ens'] }, { fields: ['address'] }],
}
);
const readByAddress = async (address: string): Promise<ENSModel> => {
let result = await model.findOne({
where: { address },
});
return result?.toJSON() as ENSModel;
};
const readByAddress = async (address: string): Promise<ENSModel> => {
let result = await model.findOne({
where: { address },
});
return result?.toJSON() as ENSModel;
};
const readByENS = async (ens: string): Promise<ENSModel> => {
let result = await model.findOne({
where: { ens },
});
return result?.toJSON() as ENSModel;
};
const readByENS = async (ens: string): Promise<ENSModel> => {
let result = await model.findOne({
where: { ens },
});
return result?.toJSON() as ENSModel;
};
const update = async (ens: string, address: string) => {
const result = await model.findOne({
where: { ens },
});
const update = async (ens: string, address: string) => {
const result = await model.findOne({
where: { ens },
});
if (result) {
return result.update({
ens,
address,
});
}
if (result) {
return result.update({
ens,
address,
});
}
return model.create({
ens,
address,
});
};
return model.create({
ens,
address,
});
};
return {
model,
readByENS,
readByAddress,
update,
};
return {
model,
readByENS,
readByAddress,
update,
};
};
export default ens;

View File

@@ -2,92 +2,92 @@ import { Sequelize, STRING } from 'sequelize';
import { Mutex } from 'async-mutex';
type InterepGroupModel = {
provider: string;
name: string;
root_hash: string;
provider: string;
name: string;
root_hash: string;
};
const mutex = new Mutex();
const interepGroups = (sequelize: Sequelize) => {
const model = sequelize.define(
'interep_groups',
{
name: {
type: STRING,
allowNull: false,
},
provider: {
type: STRING,
allowNull: false,
},
root_hash: {
type: STRING,
allowNull: false,
},
const model = sequelize.define(
'interep_groups',
{
name: {
type: STRING,
allowNull: false,
},
provider: {
type: STRING,
allowNull: false,
},
root_hash: {
type: STRING,
allowNull: false,
},
},
{
indexes: [
{ fields: ['root_hash'] },
{ fields: ['provider'] },
{ fields: ['name'] },
{ fields: ['provider', 'name'], unique: true },
],
}
);
const findOneByHash = async (root_hash: string): Promise<InterepGroupModel | null> => {
let result = await model.findOne({
where: {
root_hash,
},
});
return result?.toJSON() as InterepGroupModel;
};
const addHash = async (root_hash: string, provider: string, name: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
provider,
name,
},
{
indexes: [
{ fields: ['root_hash'] },
{ fields: ['provider'] },
{ fields: ['name'] },
{ fields: ['provider', 'name'], unique: true },
],
}
);
});
const findOneByHash = async (root_hash: string): Promise<InterepGroupModel | null> => {
let result = await model.findOne({
where: {
root_hash,
},
if (!result) {
return model.create({
root_hash,
provider,
name,
});
return result?.toJSON() as InterepGroupModel;
};
const addHash = async (root_hash: string, provider: string, name: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
provider,
name,
},
});
if (!result) {
return model.create({
root_hash,
provider,
name,
});
} else {
return result.update({
root_hash,
});
}
} else {
return result.update({
root_hash,
});
};
}
});
};
const getGroup = async (provider: string, name: string): Promise<InterepGroupModel> => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
provider,
name,
},
});
const getGroup = async (provider: string, name: string): Promise<InterepGroupModel> => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
provider,
name,
},
});
return result?.toJSON() as InterepGroupModel;
});
};
return result?.toJSON() as InterepGroupModel;
});
};
return {
model,
findOneByHash,
addHash,
getGroup,
};
return {
model,
findOneByHash,
addHash,
getGroup,
};
};
export default interepGroups;

View File

@@ -1,76 +1,76 @@
import { Sequelize, BIGINT, STRING } from 'sequelize';
type LinkPreviewModel = {
link: string;
title: string;
description: string;
image: string;
mediaType: string;
contentType: string;
favicon: string;
link: string;
title: string;
description: string;
image: string;
mediaType: string;
contentType: string;
favicon: string;
};
const linkPreview = (sequelize: Sequelize) => {
const model = sequelize.define(
'link',
{
title: {
type: STRING,
},
image: {
type: STRING,
},
description: {
type: STRING,
},
link: {
type: STRING,
unique: true,
},
mediaType: {
type: STRING,
},
contentType: {
type: STRING,
},
favicon: {
type: STRING,
},
},
{
indexes: [{ fields: ['link'], unique: true }],
}
);
const model = sequelize.define(
'link',
{
title: {
type: STRING,
},
image: {
type: STRING,
},
description: {
type: STRING,
},
link: {
type: STRING,
unique: true,
},
mediaType: {
type: STRING,
},
contentType: {
type: STRING,
},
favicon: {
type: STRING,
},
},
{
indexes: [{ fields: ['link'], unique: true }],
}
);
const read = async (link: string): Promise<LinkPreviewModel> => {
let result = await model.findOne({
where: {
link,
},
});
const read = async (link: string): Promise<LinkPreviewModel> => {
let result = await model.findOne({
where: {
link,
},
});
return result?.toJSON() as LinkPreviewModel;
};
return result?.toJSON() as LinkPreviewModel;
};
const update = async (linkPreview: LinkPreviewModel) => {
const result = await model.findOne({
where: {
link: linkPreview.link,
},
});
const update = async (linkPreview: LinkPreviewModel) => {
const result = await model.findOne({
where: {
link: linkPreview.link,
},
});
if (result) {
return result.update(linkPreview);
}
if (result) {
return result.update(linkPreview);
}
return model.create(linkPreview);
};
return model.create(linkPreview);
};
return {
model,
read,
update,
};
return {
model,
read,
update,
};
};
export default linkPreview;

View File

@@ -1,56 +1,56 @@
import { Sequelize, BIGINT, STRING } from 'sequelize';
type MerkleRootModel = {
root_hash: string;
group_id: string;
root_hash: string;
group_id: string;
};
const merkleRoot = (sequelize: Sequelize) => {
const model = sequelize.define(
'merkle_root',
{
root_hash: {
type: STRING,
},
group_id: {
type: STRING,
},
},
{
indexes: [{ fields: ['root_hash'], unique: true }, { fields: ['group_id'] }],
}
);
const model = sequelize.define(
'merkle_root',
{
root_hash: {
type: STRING,
},
group_id: {
type: STRING,
},
},
{
indexes: [{ fields: ['root_hash'], unique: true }, { fields: ['group_id'] }],
}
);
const getGroupByRoot = async (root_hash: string): Promise<MerkleRootModel> => {
let result = await model.findOne({
where: {
root_hash,
},
});
const getGroupByRoot = async (root_hash: string): Promise<MerkleRootModel> => {
let result = await model.findOne({
where: {
root_hash,
},
});
return result?.toJSON() as MerkleRootModel;
};
return result?.toJSON() as MerkleRootModel;
};
const addRoot = async (root_hash: string, group_id: string) => {
const exist = await model.findOne({
where: {
root_hash,
},
});
const addRoot = async (root_hash: string, group_id: string) => {
const exist = await model.findOne({
where: {
root_hash,
},
});
if (!exist) {
return model.create({
root_hash,
group_id,
});
}
};
if (!exist) {
return model.create({
root_hash,
group_id,
});
}
};
return {
model,
addRoot,
getGroupByRoot,
};
return {
model,
addRoot,
getGroupByRoot,
};
};
export default merkleRoot;

View File

@@ -2,90 +2,90 @@ import { BIGINT, QueryTypes, Sequelize, STRING } from 'sequelize';
import { Mutex } from 'async-mutex';
type MetaModel = {
reference: string;
replyCount: number;
likeCount: number;
repostCount: number;
postCount: number;
reference: string;
replyCount: number;
likeCount: number;
repostCount: number;
postCount: number;
};
const mutex = new Mutex();
const emptyMeta = {
replyCount: 0,
likeCount: 0,
repostCount: 0,
postCount: 0,
replyCount: 0,
likeCount: 0,
repostCount: 0,
postCount: 0,
};
const meta = (sequelize: Sequelize) => {
const model = sequelize.define(
'meta',
{
reference: {
type: STRING,
allowNull: false,
primaryKey: true,
},
postCount: {
type: BIGINT,
},
replyCount: {
type: BIGINT,
},
likeCount: {
type: BIGINT,
},
repostCount: {
type: BIGINT,
},
},
{
indexes: [{ fields: ['reference'], unique: true }],
}
);
const model = sequelize.define(
'meta',
{
reference: {
type: STRING,
allowNull: false,
primaryKey: true,
},
postCount: {
type: BIGINT,
},
replyCount: {
type: BIGINT,
},
likeCount: {
type: BIGINT,
},
repostCount: {
type: BIGINT,
},
},
{
indexes: [{ fields: ['reference'], unique: true }],
}
);
const findOne = async (reference: string, context = ''): Promise<any | null> => {
const result = await sequelize.query(
`
const findOne = async (reference: string, context = ''): Promise<any | null> => {
const result = await sequelize.query(
`
${selectMetaQuery}
`,
{
replacements: {
context: context || '',
reference,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
context: context || '',
reference,
},
type: QueryTypes.SELECT,
}
);
const values: any[] = [];
const values: any[] = [];
for (let r of result) {
const row = r as any;
const meta = {
liked: row.liked,
reposted: row.reposted,
replyCount: row.replyCount ? Number(row.replyCount) : 0,
repostCount: row.repostCount ? Number(row.repostCount) : 0,
likeCount: row.likeCount ? Number(row.likeCount) : 0,
postCount: row.postCount ? Number(row.postCount) : 0,
};
values.push(meta);
}
for (let r of result) {
const row = r as any;
const meta = {
liked: row.liked,
reposted: row.reposted,
replyCount: row.replyCount ? Number(row.replyCount) : 0,
repostCount: row.repostCount ? Number(row.repostCount) : 0,
likeCount: row.likeCount ? Number(row.likeCount) : 0,
postCount: row.postCount ? Number(row.postCount) : 0,
};
values.push(meta);
}
return values[0]
? values[0]
: {
liked: null,
reposted: null,
...emptyMeta,
};
};
return values[0]
? values[0]
: {
liked: null,
reposted: null,
...emptyMeta,
};
};
const findTags = async (offset = 0, limit = 20) => {
const result = await sequelize.query(
`
const findTags = async (offset = 0, limit = 20) => {
const result = await sequelize.query(
`
SELECT
"reference",
"postCount"
@@ -94,68 +94,68 @@ const meta = (sequelize: Sequelize) => {
ORDER BY "postCount" DESC
LIMIT :limit OFFSET :offset
`,
{
replacements: {
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
return result.map((r: any) => ({
tagName: r.reference,
postCount: Number(r.postCount),
}));
return result.map((r: any) => ({
tagName: r.reference,
postCount: Number(r.postCount),
}));
};
const update = async (record: MetaModel) => {
return model.create(record);
};
return {
model,
findOne,
findTags,
update,
addLike: makeIncrementer('likeCount', 1),
addReply: makeIncrementer('replyCount', 1),
addRepost: makeIncrementer('repostCount', 1),
addPost: makeIncrementer('postCount', 1),
removeLike: makeIncrementer('likeCount', -1),
removeReply: makeIncrementer('replyCount', -1),
removeRepost: makeIncrementer('repostCount', -1),
removePost: makeIncrementer('postCount', -1),
};
function makeIncrementer(key: string, delta: number) {
return async (reference: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
reference,
},
});
if (result) {
const data = result.toJSON() as MetaModel;
return result.update({
...data,
// @ts-ignore
[key]: Math.max(0, (Number(data[key]) || 0) + delta),
});
}
const res = await model.create({
reference,
...emptyMeta,
[key]: Math.max(0, delta),
});
return res;
});
};
const update = async (record: MetaModel) => {
return model.create(record);
};
return {
model,
findOne,
findTags,
update,
addLike: makeIncrementer('likeCount', 1),
addReply: makeIncrementer('replyCount', 1),
addRepost: makeIncrementer('repostCount', 1),
addPost: makeIncrementer('postCount', 1),
removeLike: makeIncrementer('likeCount', -1),
removeReply: makeIncrementer('replyCount', -1),
removeRepost: makeIncrementer('repostCount', -1),
removePost: makeIncrementer('postCount', -1),
};
function makeIncrementer(key: string, delta: number) {
return async (reference: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
reference,
},
});
if (result) {
const data = result.toJSON() as MetaModel;
return result.update({
...data,
// @ts-ignore
[key]: Math.max(0, (Number(data[key]) || 0) + delta),
});
}
const res = await model.create({
reference,
...emptyMeta,
[key]: Math.max(0, delta),
});
return res;
});
};
}
}
};
export default meta;

View File

@@ -1,90 +1,90 @@
import { BIGINT, ModelCtor, QueryTypes, Sequelize, STRING } from 'sequelize';
import {
Message,
MessageType,
ModerationJSON,
PostJSON,
PostMessageSubType,
Message,
MessageType,
ModerationJSON,
PostJSON,
PostMessageSubType,
} from '../util/message';
import { PostModel } from './posts';
type ModerationModel = {
messageId: string;
hash: string;
creator: string;
type: string;
subtype: string;
createdAt: number;
reference: string;
messageId: string;
hash: string;
creator: string;
type: string;
subtype: string;
createdAt: number;
reference: string;
};
const moderations = (sequelize: Sequelize) => {
const model = sequelize.define(
'moderations',
{
messageId: {
type: STRING,
allowNull: false,
},
hash: {
type: STRING,
allowNull: false,
primaryKey: true,
},
creator: {
type: STRING,
allowNull: false,
},
type: {
type: STRING,
allowNull: false,
},
subtype: {
type: STRING,
},
createdAt: {
type: BIGINT,
allowNull: false,
},
reference: {
type: STRING,
},
},
{
indexes: [
{ fields: ['creator'] },
{ fields: ['subtype'] },
{ fields: ['hash'], unique: true },
{ fields: ['messageId'], unique: true },
],
}
);
const model = sequelize.define(
'moderations',
{
messageId: {
type: STRING,
allowNull: false,
},
hash: {
type: STRING,
allowNull: false,
primaryKey: true,
},
creator: {
type: STRING,
allowNull: false,
},
type: {
type: STRING,
allowNull: false,
},
subtype: {
type: STRING,
},
createdAt: {
type: BIGINT,
allowNull: false,
},
reference: {
type: STRING,
},
},
{
indexes: [
{ fields: ['creator'] },
{ fields: ['subtype'] },
{ fields: ['hash'], unique: true },
{ fields: ['messageId'], unique: true },
],
}
);
const findOne = async (hash: string): Promise<ModerationModel | null> => {
let result: any = await model.findOne({
where: {
hash,
},
});
const findOne = async (hash: string): Promise<ModerationModel | null> => {
let result: any = await model.findOne({
where: {
hash,
},
});
if (!result) return null;
if (!result) return null;
const json = result.toJSON() as ModerationModel;
const json = result.toJSON() as ModerationModel;
return json;
};
return json;
};
const remove = async (hash: string) => {
return model.destroy({
where: {
hash,
},
});
};
const remove = async (hash: string) => {
return model.destroy({
where: {
hash,
},
});
};
const findThreadModeration = async (message_id: string) => {
const result = await sequelize.query(
`
const findThreadModeration = async (message_id: string) => {
const result = await sequelize.query(
`
SELECT
m."messageId",
m.creator,
@@ -99,51 +99,51 @@ const moderations = (sequelize: Sequelize) => {
JOIN moderations m ON m.reference = t.message_id AND m.creator = p.creator AND m.subtype IN ('THREAD_HIDE_BLOCK', 'THREAD_ONLY_MENTION', 'THREAD_SHOW_FOLLOW')
WHERE t.message_id = :message_id
`,
{
replacements: {
message_id,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
message_id,
},
type: QueryTypes.SELECT,
}
);
if (result) {
return result;
}
if (result) {
return result;
}
return null;
};
return null;
};
const findAllByReference = async (
reference: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<ModerationJSON[]> => {
let result = await model.findAll({
where: {
reference,
},
offset,
limit,
order: [['createdAt', order]],
});
const findAllByReference = async (
reference: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<ModerationJSON[]> => {
let result = await model.findAll({
where: {
reference,
},
offset,
limit,
order: [['createdAt', order]],
});
return result.map((r: any) => r.toJSON() as ModerationJSON);
};
return result.map((r: any) => r.toJSON() as ModerationJSON);
};
const createModeration = async (record: ModerationModel) => {
return model.create(record);
};
const createModeration = async (record: ModerationModel) => {
return model.create(record);
};
return {
model,
findOne,
remove,
findAllByReference,
findThreadModeration,
createModeration,
};
return {
model,
findOne,
remove,
findAllByReference,
findThreadModeration,
createModeration,
};
};
export default moderations;

View File

@@ -3,137 +3,137 @@ import { MessageType, PostJSON, PostMessageSubType } from '../util/message';
import { Mutex } from 'async-mutex';
import bodyParser from 'body-parser';
import {
globalModClause,
globalVisibilityClause,
notBlockedClause,
replyModerationClause,
globalModClause,
globalVisibilityClause,
notBlockedClause,
replyModerationClause,
} from '../util/sql';
import config from '../util/config';
const mutex = new Mutex();
export type PostModel = {
messageId: string;
hash: string;
proof?: string;
signals?: string;
creator: string;
type: string;
subtype: string;
createdAt: number;
topic: string;
title: string;
content: string;
reference: string;
attachment: string;
messageId: string;
hash: string;
proof?: string;
signals?: string;
creator: string;
type: string;
subtype: string;
createdAt: number;
topic: string;
title: string;
content: string;
reference: string;
attachment: string;
};
const posts = (sequelize: Sequelize) => {
const model = sequelize.define(
'posts',
{
hash: {
type: STRING,
allowNull: false,
primaryKey: true,
},
messageId: {
type: STRING,
allowNull: false,
},
creator: {
type: STRING,
allowNull: false,
},
proof: {
type: STRING(65535),
},
signals: {
type: STRING(65535),
},
type: {
type: STRING,
allowNull: false,
},
subtype: {
type: STRING,
},
createdAt: {
type: BIGINT,
allowNull: false,
},
topic: {
type: STRING,
allowNull: false,
},
title: {
type: STRING(4095),
allowNull: false,
},
content: {
type: STRING(65535),
allowNull: false,
},
reference: {
type: STRING,
},
attachment: {
type: STRING(4095),
},
},
{
indexes: [
{ fields: ['creator'] },
{ fields: ['subtype'] },
{ fields: ['topic'] },
{ fields: ['reference'] },
{ fields: ['hash'], unique: true },
{ fields: ['messageId'], unique: true },
],
}
);
const model = sequelize.define(
'posts',
{
hash: {
type: STRING,
allowNull: false,
primaryKey: true,
},
messageId: {
type: STRING,
allowNull: false,
},
creator: {
type: STRING,
allowNull: false,
},
proof: {
type: STRING(65535),
},
signals: {
type: STRING(65535),
},
type: {
type: STRING,
allowNull: false,
},
subtype: {
type: STRING,
},
createdAt: {
type: BIGINT,
allowNull: false,
},
topic: {
type: STRING,
allowNull: false,
},
title: {
type: STRING(4095),
allowNull: false,
},
content: {
type: STRING(65535),
allowNull: false,
},
reference: {
type: STRING,
},
attachment: {
type: STRING(4095),
},
},
{
indexes: [
{ fields: ['creator'] },
{ fields: ['subtype'] },
{ fields: ['topic'] },
{ fields: ['reference'] },
{ fields: ['hash'], unique: true },
{ fields: ['messageId'], unique: true },
],
}
);
const remove = async (hash: string) => {
return model.destroy({
where: {
hash,
},
});
};
const remove = async (hash: string) => {
return model.destroy({
where: {
hash,
},
});
};
const findRoot = async (messageId: string): Promise<string | null> => {
const result = await model.findOne({
where: {
messageId,
},
});
const findRoot = async (messageId: string): Promise<string | null> => {
const result = await model.findOne({
where: {
messageId,
},
});
if (result) {
// @ts-ignore
const json: PostModel = result.toJSON();
if (json.reference && json.subtype === 'REPLY') {
return findRoot(json.reference);
}
if (result) {
// @ts-ignore
const json: PostModel = result.toJSON();
if (json.reference && json.subtype === 'REPLY') {
return findRoot(json.reference);
}
if (json.reference && json.subtype === 'M_REPLY') {
return findRoot(json.reference);
}
if (json.reference && json.subtype === 'M_REPLY') {
return findRoot(json.reference);
}
if (json.reference && json.subtype === 'REPOST') {
return findRoot(json.reference);
}
if (json.reference && json.subtype === 'REPOST') {
return findRoot(json.reference);
}
if (!json.reference) {
return json.messageId;
}
}
if (!json.reference) {
return json.messageId;
}
}
return null;
};
return null;
};
const findOne = async (hash: string, context?: string): Promise<PostJSON | null> => {
const result = await sequelize.query(
`
const findOne = async (hash: string, context?: string): Promise<PostJSON | null> => {
const result = await sequelize.query(
`
${selectJoinQuery}
WHERE (
p.hash = :hash
@@ -141,37 +141,37 @@ const posts = (sequelize: Sequelize) => {
AND p."creator" NOT IN (SELECT name FROM connections WHERE name = p.creator AND creator = :context AND subtype = 'BLOCK')
)
`,
{
replacements: {
context: context || '',
hash,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
context: context || '',
hash,
},
type: QueryTypes.SELECT,
}
);
const values: PostJSON[] = [];
const values: PostJSON[] = [];
for (let r of result) {
const post = inflateResultToPostJSON(r);
if (post.createdAt > 0) {
values.push(post);
}
}
for (let r of result) {
const post = inflateResultToPostJSON(r);
if (post.createdAt > 0) {
values.push(post);
}
}
return values[0];
};
return values[0];
};
const findAllPosts = async (
creator?: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC',
showAll = false
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
const findAllPosts = async (
creator?: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC',
showAll = false
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
${selectJoinQuery}
WHERE (
(p.type = 'POST' AND p.subtype IN ('', 'M_POST', 'REPOST'))
@@ -184,36 +184,36 @@ const posts = (sequelize: Sequelize) => {
ORDER BY p."createdAt" ${order}
LIMIT :limit OFFSET :offset
`,
{
replacements: {
context: context || '',
creator: creator || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
context: context || '',
creator: creator || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
const values: PostJSON[] = [];
const values: PostJSON[] = [];
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
return values;
};
return values;
};
const findAllRepliesFromCreator = async (
creator?: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
const findAllRepliesFromCreator = async (
creator?: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
${selectJoinQuery}
WHERE (
p.type = 'POST'
@@ -226,35 +226,35 @@ const posts = (sequelize: Sequelize) => {
ORDER BY p."createdAt" ${order}
LIMIT :limit OFFSET :offset
`,
{
replacements: {
context: context || '',
creator: creator || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
context: context || '',
creator: creator || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
const values: PostJSON[] = [];
const values: PostJSON[] = [];
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
return values;
};
return values;
};
const getHomeFeed = async (
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
const getHomeFeed = async (
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
${selectJoinQuery}
WHERE (
p.subtype != 'REPLY'
@@ -269,37 +269,37 @@ const posts = (sequelize: Sequelize) => {
ORDER BY p."createdAt" ${order}
LIMIT :limit OFFSET :offset
`,
{
replacements: {
context: context || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
context: context || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
const values: PostJSON[] = [];
const values: PostJSON[] = [];
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
return values;
};
return values;
};
const findAllReplies = async (
reference: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'ASC',
tweetId = '',
unmoderated = false
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
const findAllReplies = async (
reference: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'ASC',
tweetId = '',
unmoderated = false
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
${selectJoinQuery}
WHERE (
(
@@ -313,35 +313,35 @@ const posts = (sequelize: Sequelize) => {
ORDER BY p."createdAt" ${order}
LIMIT :limit OFFSET :offset
`,
{
replacements: {
reference,
context: context || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
reference,
context: context || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
const values: PostJSON[] = [];
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
const values: PostJSON[] = [];
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
return values;
};
return values;
};
const findAllLikedPostsByCreator = async (
creator: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
const findAllLikedPostsByCreator = async (
creator: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC'
): Promise<PostJSON[]> => {
const result = await sequelize.query(
`
${selectLikedPostsQuery}
WHERE (
p."createdAt" != -1
@@ -354,196 +354,193 @@ const posts = (sequelize: Sequelize) => {
ORDER BY p."createdAt" ${order}
LIMIT :limit OFFSET :offset
`,
{
replacements: {
creator,
context: context || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
creator,
context: context || '',
limit,
offset,
},
type: QueryTypes.SELECT,
}
);
const values: PostJSON[] = [];
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
const values: PostJSON[] = [];
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
return values;
};
const findLastTweetInConversation = async (id: string) => {
const result = await model.findOne({
where: {
[Op.or]: [
{
reference: id,
type: '@TWEET@',
},
],
},
order: [['createdAt', 'DESC']],
limit: 1,
});
return result?.toJSON();
};
const createTwitterPosts = async (records: PostModel[]) => {
return mutex.runExclusive(async () => {
for (let record of records) {
if (record.type !== '@TWEET@') continue;
const topic = `https://twitter.com/${record.creator}/status/${record.messageId}`;
const result = await model.findOne({
where: {
[Op.or]: [
{
topic: topic,
subtype: [PostMessageSubType.MirrorPost, PostMessageSubType.MirrorReply],
},
{
messageId: record.messageId,
type: '@TWEET@',
},
],
},
});
if (result) {
continue;
}
return values;
};
await model.create(record);
}
});
};
const findLastTweetInConversation = async (id: string) => {
const result = await model.findOne({
where: {
[Op.or]: [
{
reference: id,
type: '@TWEET@',
},
],
},
order: [['createdAt', 'DESC']],
limit: 1,
});
const createPost = async (record: PostModel) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
hash: record.hash,
},
});
return result?.toJSON();
};
if (result) {
const json = (await result.toJSON()) as PostModel;
if (json.createdAt < 0) {
// @ts-ignore
await result.changed('createdAt', true);
await result.set('createdAt', record.createdAt, { raw: true });
await result.save({
fields: ['createdAt'],
});
return result.update(record);
}
}
const createTwitterPosts = async (records: PostModel[]) => {
return mutex.runExclusive(async () => {
for (let record of records) {
if (record.type !== '@TWEET@') continue;
return model.create(record);
});
};
const topic = `https://twitter.com/${record.creator}/status/${record.messageId}`;
const result = await model.findOne({
where: {
[Op.or]: [
{
topic: topic,
subtype: [
PostMessageSubType.MirrorPost,
PostMessageSubType.MirrorReply,
],
},
{
messageId: record.messageId,
type: '@TWEET@',
},
],
},
});
const ensurePost = async (messageId: string) => {
return mutex.runExclusive(async () => {
const [creator, hash] = messageId.split('/');
const result = await model.findOne({
where: {
hash: hash || creator,
},
});
if (result) {
continue;
}
if (!result) {
const emptyModel: PostModel = {
messageId: messageId,
hash: hash || creator,
type: MessageType.Post,
subtype: PostMessageSubType.Default,
creator: hash ? creator : '',
createdAt: -1,
topic: '',
title: '',
content: '',
reference: '',
attachment: '',
};
return model.create(emptyModel);
}
});
};
await model.create(record);
}
});
};
const createPost = async (record: PostModel) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
hash: record.hash,
},
});
if (result) {
const json = (await result.toJSON()) as PostModel;
if (json.createdAt < 0) {
// @ts-ignore
await result.changed('createdAt', true);
await result.set('createdAt', record.createdAt, { raw: true });
await result.save({
fields: ['createdAt'],
});
return result.update(record);
}
}
return model.create(record);
});
};
const ensurePost = async (messageId: string) => {
return mutex.runExclusive(async () => {
const [creator, hash] = messageId.split('/');
const result = await model.findOne({
where: {
hash: hash || creator,
},
});
if (!result) {
const emptyModel: PostModel = {
messageId: messageId,
hash: hash || creator,
type: MessageType.Post,
subtype: PostMessageSubType.Default,
creator: hash ? creator : '',
createdAt: -1,
topic: '',
title: '',
content: '',
reference: '',
attachment: '',
};
return model.create(emptyModel);
}
});
};
return {
model,
remove,
findOne,
findRoot,
findAllPosts,
findAllRepliesFromCreator,
findAllLikedPostsByCreator,
findLastTweetInConversation,
findAllReplies,
getHomeFeed,
createTwitterPosts,
createPost,
ensurePost,
};
return {
model,
remove,
findOne,
findRoot,
findAllPosts,
findAllRepliesFromCreator,
findAllLikedPostsByCreator,
findLastTweetInConversation,
findAllReplies,
getHomeFeed,
createTwitterPosts,
createPost,
ensurePost,
};
};
export default posts;
export function inflateResultToPostJSON(r: any): PostJSON {
const json = r as any;
const meta = {
replyCount: +json?.replyCount || 0,
likeCount: +json?.likeCount || 0,
repostCount: +json?.repostCount || 0,
liked: json?.liked,
reposted: json?.reposted,
blocked: json?.blocked,
interepProvider: json?.interepProvider,
interepGroup: json?.interepGroup,
rootId: json?.rootId,
moderation: json?.moderation || null,
modblockedctx: json?.modblockedctx || null,
modfollowedctx: json?.modfollowedctx || null,
modmentionedctx: json?.modmentionedctx || null,
modLikedPost: json?.modLikedPost || null,
modBlockedPost: json?.modBlockedPost || null,
modBlockedUser: json?.modBlockedUser || null,
modFollowerUser: json?.modFollowerUser || null,
};
const json = r as any;
const meta = {
replyCount: +json?.replyCount || 0,
likeCount: +json?.likeCount || 0,
repostCount: +json?.repostCount || 0,
liked: json?.liked,
reposted: json?.reposted,
blocked: json?.blocked,
interepProvider: json?.interepProvider,
interepGroup: json?.interepGroup,
rootId: json?.rootId,
moderation: json?.moderation || null,
modblockedctx: json?.modblockedctx || null,
modfollowedctx: json?.modfollowedctx || null,
modmentionedctx: json?.modmentionedctx || null,
modLikedPost: json?.modLikedPost || null,
modBlockedPost: json?.modBlockedPost || null,
modBlockedUser: json?.modBlockedUser || null,
modFollowerUser: json?.modFollowerUser || null,
};
if (json.subtype === PostMessageSubType.Repost) {
meta.replyCount = +json?.rpReplyCount || 0;
meta.likeCount = +json?.rpLikeCount || 0;
meta.repostCount = +json?.rpRepostCount || 0;
meta.liked = json?.rpLiked || null;
meta.blocked = json?.rpBLocked || null;
meta.reposted = json?.rpReposted || null;
meta.interepProvider = json?.rpInterepProvider || null;
meta.interepGroup = json?.rpInterepGroup || null;
}
if (json.subtype === PostMessageSubType.Repost) {
meta.replyCount = +json?.rpReplyCount || 0;
meta.likeCount = +json?.rpLikeCount || 0;
meta.repostCount = +json?.rpRepostCount || 0;
meta.liked = json?.rpLiked || null;
meta.blocked = json?.rpBLocked || null;
meta.reposted = json?.rpReposted || null;
meta.interepProvider = json?.rpInterepProvider || null;
meta.interepGroup = json?.rpInterepGroup || null;
}
return {
type: json.type as MessageType,
subtype: json.subtype as PostMessageSubType,
messageId: json.creator ? `${json.creator}/${json.hash}` : json.hash,
hash: json.hash,
createdAt: json.createdAt,
payload: {
topic: json.topic,
title: json.title,
content: json.content,
reference: json.reference,
attachment: json.attachment,
},
meta: meta,
};
return {
type: json.type as MessageType,
subtype: json.subtype as PostMessageSubType,
messageId: json.creator ? `${json.creator}/${json.hash}` : json.hash,
hash: json.hash,
createdAt: json.createdAt,
payload: {
topic: json.topic,
title: json.title,
content: json.content,
reference: json.reference,
attachment: json.attachment,
},
meta: meta,
};
}
const selectJoinQuery = `
@@ -598,11 +595,11 @@ const selectJoinQuery = `
LEFT JOIN connections modblockeduser ON modblockeduser."messageId" = (SELECT "messageId" FROM connections WHERE subtype = 'BLOCK' AND name = p."creator" AND creator = root.creator LIMIT 1)
LEFT JOIN connections modfolloweduser ON modfolloweduser."messageId" = (SELECT "messageId" FROM connections WHERE subtype = 'FOLLOW' AND name = p."creator" AND creator = root.creator LIMIT 1)
LEFT JOIN moderations gmodblocked ON gmodblocked."messageId" = (SELECT "messageId" FROM moderations WHERE subtype = 'BLOCK' AND reference = p."messageId" AND creator IN (${config.moderators
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
LEFT JOIN connections gmodblockeduser ON gmodblockeduser."messageId" = (SELECT "messageId" FROM connections WHERE subtype = 'BLOCK' AND name = p."creator" AND creator IN (${config.moderators
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
LEFT JOIN posts rp ON rp."messageId" = (SELECT "messageId" from posts WHERE p."messageId" = reference AND creator = :context AND subtype = 'REPOST' LIMIT 1)
LEFT JOIN posts rprp ON rprp."messageId" = (SELECT "messageId" from posts WHERE reference = p.reference AND creator = :context AND subtype = 'REPOST' AND p.subtype = 'REPOST' LIMIT 1)
LEFT JOIN meta mt ON mt."reference" = p."messageId"
@@ -666,11 +663,11 @@ const selectLikedPostsQuery = `
LEFT JOIN moderations modblocked ON modblocked."messageId" = (SELECT "messageId" FROM moderations WHERE subtype = 'BLOCK' AND reference = p."messageId" AND creator = root.creator LIMIT 1)
LEFT JOIN connections modblockeduser ON modblockeduser."messageId" = (SELECT "messageId" FROM connections WHERE subtype = 'BLOCK' AND name = p."creator" AND creator = root.creator LIMIT 1)
LEFT JOIN moderations gmodblocked ON gmodblocked."messageId" = (SELECT "messageId" FROM moderations WHERE subtype = 'BLOCK' AND reference = p."messageId" AND creator IN (${config.moderators
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
LEFT JOIN connections gmodblockeduser ON gmodblockeduser."messageId" = (SELECT "messageId" FROM connections WHERE subtype = 'BLOCK' AND name = p."creator" AND creator IN (${config.moderators
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
LEFT JOIN connections modfolloweduser ON modfolloweduser."messageId" = (SELECT "messageId" FROM connections WHERE subtype = 'FOLLOW' AND name = p."creator" AND creator = root.creator LIMIT 1)
LEFT JOIN posts rp ON rp."messageId" = (SELECT "messageId" from posts WHERE p."messageId" = reference AND creator = :context AND subtype = 'REPOST' LIMIT 1)
LEFT JOIN posts rprp ON rprp."messageId" = (SELECT "messageId" from posts WHERE reference = p.reference AND creator = :context AND subtype = 'REPOST' AND p.subtype = 'REPOST' LIMIT 1)

View File

@@ -2,103 +2,103 @@ import { BIGINT, Sequelize, STRING } from 'sequelize';
import { ProfileMessageSubType } from '../util/message';
type ProfileModel = {
messageId: string;
hash: string;
creator: string;
type: string;
subtype: string;
createdAt: number;
key: string;
value: string;
messageId: string;
hash: string;
creator: string;
type: string;
subtype: string;
createdAt: number;
key: string;
value: string;
};
export type UserProfile = {
name: string;
bio: string;
coverImage: string;
profileImage: string;
twitterVerification: string;
website: string;
name: string;
bio: string;
coverImage: string;
profileImage: string;
twitterVerification: string;
website: string;
};
const profiles = (sequelize: Sequelize) => {
const model = sequelize.define(
'profiles',
{
messageId: {
type: STRING,
allowNull: false,
},
hash: {
type: STRING,
allowNull: false,
primaryKey: true,
},
creator: {
type: STRING,
allowNull: false,
},
type: {
type: STRING,
allowNull: false,
},
subtype: {
type: STRING,
},
createdAt: {
type: BIGINT,
allowNull: false,
},
key: {
type: STRING,
},
value: {
type: STRING,
},
},
{
indexes: [
{ fields: ['creator'] },
{ fields: ['subtype'] },
{ fields: ['key'] },
{ fields: ['hash'], unique: true },
{ fields: ['messageId'], unique: true },
],
}
);
const model = sequelize.define(
'profiles',
{
messageId: {
type: STRING,
allowNull: false,
},
hash: {
type: STRING,
allowNull: false,
primaryKey: true,
},
creator: {
type: STRING,
allowNull: false,
},
type: {
type: STRING,
allowNull: false,
},
subtype: {
type: STRING,
},
createdAt: {
type: BIGINT,
allowNull: false,
},
key: {
type: STRING,
},
value: {
type: STRING,
},
},
{
indexes: [
{ fields: ['creator'] },
{ fields: ['subtype'] },
{ fields: ['key'] },
{ fields: ['hash'], unique: true },
{ fields: ['messageId'], unique: true },
],
}
);
const remove = async (hash: string) => {
return model.destroy({
where: {
hash,
},
});
};
const remove = async (hash: string) => {
return model.destroy({
where: {
hash,
},
});
};
const findOne = async (hash: string): Promise<ProfileModel | null> => {
let result: any = await model.findOne({
where: {
hash,
},
});
const findOne = async (hash: string): Promise<ProfileModel | null> => {
let result: any = await model.findOne({
where: {
hash,
},
});
if (!result) return null;
if (!result) return null;
const json = result.toJSON() as ProfileModel;
const json = result.toJSON() as ProfileModel;
return json;
};
return json;
};
const createProfile = async (record: ProfileModel) => {
return model.create(record);
};
const createProfile = async (record: ProfileModel) => {
return model.create(record);
};
return {
model,
remove,
findOne,
createProfile,
};
return {
model,
remove,
findOne,
createProfile,
};
};
export default profiles;

View File

@@ -1,104 +1,104 @@
import { BIGINT, Sequelize, STRING } from 'sequelize';
type RecordModel = {
soul: string;
field: string;
value: string;
relation: string;
state: number;
soul: string;
field: string;
value: string;
relation: string;
state: number;
};
const records = (sequelize: Sequelize) => {
const model = sequelize.define(
'records',
const model = sequelize.define(
'records',
{
soul: {
type: STRING(4095),
},
field: {
type: STRING(4095),
},
value: {
type: STRING(65535),
},
relation: {
type: STRING(65535),
},
state: {
type: BIGINT,
},
},
{
indexes: [
{
soul: {
type: STRING(4095),
},
field: {
type: STRING(4095),
},
value: {
type: STRING(65535),
},
relation: {
type: STRING(65535),
},
state: {
type: BIGINT,
},
fields: ['soul'],
},
{
indexes: [
{
fields: ['soul'],
},
{
unique: true,
fields: ['soul', 'field'],
},
],
}
);
unique: true,
fields: ['soul', 'field'],
},
],
}
);
const findOne = async (soul: string, field: string): Promise<RecordModel | null> => {
let result = await model.findOne({
where: {
soul,
field,
},
});
const findOne = async (soul: string, field: string): Promise<RecordModel | null> => {
let result = await model.findOne({
where: {
soul,
field,
},
});
return (result?.toJSON() as RecordModel) || null;
};
return (result?.toJSON() as RecordModel) || null;
};
const findAll = async (soul: string): Promise<RecordModel[]> => {
let result = await model.findAll({
where: {
soul,
},
});
const findAll = async (soul: string): Promise<RecordModel[]> => {
let result = await model.findAll({
where: {
soul,
},
});
return result.map(r => r.toJSON() as RecordModel);
};
return result.map(r => r.toJSON() as RecordModel);
};
const readAll = async (offset = 0, limit = 20): Promise<RecordModel[]> => {
let result = await model.findAll({
offset,
limit,
});
const readAll = async (offset = 0, limit = 20): Promise<RecordModel[]> => {
let result = await model.findAll({
offset,
limit,
});
return result.map(r => r.toJSON() as RecordModel);
};
return result.map(r => r.toJSON() as RecordModel);
};
const updateOrCreateRecord = async (record: RecordModel) => {
const result = await model.findOne({
where: {
soul: record.soul,
field: record.field,
},
});
const updateOrCreateRecord = async (record: RecordModel) => {
const result = await model.findOne({
where: {
soul: record.soul,
field: record.field,
},
});
const json = result?.toJSON() as RecordModel;
const json = result?.toJSON() as RecordModel;
if (json?.state >= record.state) {
return Promise.resolve();
}
if (json?.state >= record.state) {
return Promise.resolve();
}
if (result) {
return result.update(record);
}
if (result) {
return result.update(record);
}
return model.create(record);
};
return model.create(record);
};
return {
model,
findOne,
findAll,
readAll,
updateOrCreateRecord,
};
return {
model,
findOne,
findAll,
readAll,
updateOrCreateRecord,
};
};
export default records;

View File

@@ -2,127 +2,123 @@ import { Sequelize, STRING } from 'sequelize';
import { Mutex } from 'async-mutex';
type SemaphoreModel = {
id_commitment: string;
group_id: string;
root_hash: string;
id_commitment: string;
group_id: string;
root_hash: string;
};
const mutex = new Mutex();
const semaphore = (sequelize: Sequelize) => {
const model = sequelize.define(
'semaphore',
{
id_commitment: {
type: STRING,
allowNull: false,
},
group_id: {
type: STRING,
allowNull: false,
},
root_hash: {
type: STRING,
allowNull: false,
},
const model = sequelize.define(
'semaphore',
{
id_commitment: {
type: STRING,
allowNull: false,
},
group_id: {
type: STRING,
allowNull: false,
},
root_hash: {
type: STRING,
allowNull: false,
},
},
{
indexes: [{ fields: ['id_commitment'] }, { fields: ['group_id'] }, { fields: ['root_hash'] }],
}
);
const findOneByCommitment = async (id_commitment: string): Promise<SemaphoreModel | null> => {
let result = await model.findOne({
where: {
id_commitment,
},
order: [['createdAt', 'DESC']],
});
return result?.toJSON() as SemaphoreModel;
};
const findOne = async (
id_commitment: string,
group_id: string
): Promise<SemaphoreModel | null> => {
let result = await model.findOne({
where: {
id_commitment,
group_id,
},
order: [['createdAt', 'DESC']],
});
return result?.toJSON() as SemaphoreModel;
};
const findAllByCommitment = async (id_commitment: string): Promise<SemaphoreModel[]> => {
let result = await model.findAll({
where: {
id_commitment,
},
});
return result.map(r => r.toJSON()) as SemaphoreModel[];
};
const findAllByGroup = async (group: string): Promise<SemaphoreModel[]> => {
let result = await model.findAll({
where: {
group,
},
});
return result.map(r => r.toJSON()) as SemaphoreModel[];
};
const addID = async (id_commitment: string, group_id: string, root_hash: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
id_commitment,
group_id,
},
{
indexes: [
{ fields: ['id_commitment'] },
{ fields: ['group_id'] },
{ fields: ['root_hash'] },
],
}
);
});
const findOneByCommitment = async (id_commitment: string): Promise<SemaphoreModel | null> => {
let result = await model.findOne({
where: {
id_commitment,
},
order: [['createdAt', 'DESC']],
if (!result) {
return model.create({
id_commitment,
group_id,
root_hash,
});
} else {
return result.update({ root_hash });
}
});
};
return result?.toJSON() as SemaphoreModel;
};
const removeID = async (id_commitment: string, group_id: string, root_hash: string) => {
return mutex.runExclusive(async () => {
return model.destroy({
where: {
id_commitment,
group_id,
root_hash,
},
});
});
};
const findOne = async (
id_commitment: string,
group_id: string
): Promise<SemaphoreModel | null> => {
let result = await model.findOne({
where: {
id_commitment,
group_id,
},
order: [['createdAt', 'DESC']],
});
return result?.toJSON() as SemaphoreModel;
};
const findAllByCommitment = async (id_commitment: string): Promise<SemaphoreModel[]> => {
let result = await model.findAll({
where: {
id_commitment,
},
});
return result.map(r => r.toJSON()) as SemaphoreModel[];
};
const findAllByGroup = async (group: string): Promise<SemaphoreModel[]> => {
let result = await model.findAll({
where: {
group,
},
});
return result.map(r => r.toJSON()) as SemaphoreModel[];
};
const addID = async (id_commitment: string, group_id: string, root_hash: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
id_commitment,
group_id,
},
});
if (!result) {
return model.create({
id_commitment,
group_id,
root_hash,
});
} else {
return result.update({ root_hash });
}
});
};
const removeID = async (id_commitment: string, group_id: string, root_hash: string) => {
return mutex.runExclusive(async () => {
return model.destroy({
where: {
id_commitment,
group_id,
root_hash,
},
});
});
};
return {
model,
findOne,
findOneByCommitment,
findAllByCommitment,
addID,
removeID,
findAllByGroup,
};
return {
model,
findOne,
findOneByCommitment,
findAllByCommitment,
addID,
removeID,
findAllByGroup,
};
};
export default semaphore;

View File

@@ -2,52 +2,52 @@ import { Sequelize, BIGINT, STRING, QueryTypes } from 'sequelize';
import { Mutex } from 'async-mutex';
type SemaphoreCreatorModel = {
message_id: string;
provider: string;
group: string;
message_id: string;
provider: string;
group: string;
};
const mutex = new Mutex();
const semaphoreCreators = (sequelize: Sequelize) => {
const model = sequelize.define(
'semaphore_creators',
{
group: {
type: STRING,
},
provider: {
type: STRING,
},
message_id: {
type: STRING,
},
},
{
indexes: [
{ fields: ['provider'] },
{ fields: ['group'] },
{ fields: ['message_id'], unique: true },
],
}
);
const model = sequelize.define(
'semaphore_creators',
{
group: {
type: STRING,
},
provider: {
type: STRING,
},
message_id: {
type: STRING,
},
},
{
indexes: [
{ fields: ['provider'] },
{ fields: ['group'] },
{ fields: ['message_id'], unique: true },
],
}
);
const addSemaphoreCreator = async (messageId: string, provider: string, group: string) => {
return mutex.runExclusive(async () => {
const res = await model.create({
provider: provider,
group: group,
message_id: messageId,
});
const addSemaphoreCreator = async (messageId: string, provider: string, group: string) => {
return mutex.runExclusive(async () => {
const res = await model.create({
provider: provider,
group: group,
message_id: messageId,
});
return res;
});
};
return res;
});
};
return {
model,
addSemaphoreCreator,
};
return {
model,
addSemaphoreCreator,
};
};
export default semaphoreCreators;

View File

@@ -6,65 +6,65 @@ import { globalModClause, globalVisibilityClause, replyModerationClause } from '
import config from '../util/config';
type TagModel = {
tag_name: string;
message_id: string;
tag_name: string;
message_id: string;
};
const mutex = new Mutex();
const tags = (sequelize: Sequelize) => {
const model = sequelize.define(
'tags',
{
tag_name: {
type: STRING,
},
message_id: {
type: STRING,
},
},
{
indexes: [{ fields: ['tag_name', 'message_id'], unique: true }],
}
);
const model = sequelize.define(
'tags',
{
tag_name: {
type: STRING,
},
message_id: {
type: STRING,
},
},
{
indexes: [{ fields: ['tag_name', 'message_id'], unique: true }],
}
);
const addTagPost = async (tagName: string, messageId: string) => {
return mutex.runExclusive(async () => {
const res = await model.create({
tag_name: tagName,
message_id: messageId,
});
const addTagPost = async (tagName: string, messageId: string) => {
return mutex.runExclusive(async () => {
const res = await model.create({
tag_name: tagName,
message_id: messageId,
});
return res;
return res;
});
};
const removeTagPost = async (tagName: string, messageId: string) => {
return mutex.runExclusive(async () => {
try {
const res = await model.destroy({
where: {
tag_name: tagName,
message_id: messageId,
},
});
};
return res;
} catch (e) {
return false;
}
});
};
const removeTagPost = async (tagName: string, messageId: string) => {
return mutex.runExclusive(async () => {
try {
const res = await model.destroy({
where: {
tag_name: tagName,
message_id: messageId,
},
});
return res;
} catch (e) {
return false;
}
});
};
const getPostsByTag = async (
tagName: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC',
showAll = false
) => {
const result = await sequelize.query(
`
const getPostsByTag = async (
tagName: string,
context?: string,
offset = 0,
limit = 20,
order: 'DESC' | 'ASC' = 'DESC',
showAll = false
) => {
const result = await sequelize.query(
`
${selectTagPostsQuery}
WHERE (
(p."createdAt" != -1)
@@ -80,33 +80,33 @@ const tags = (sequelize: Sequelize) => {
ORDER BY p."createdAt" ${order}
LIMIT :limit OFFSET :offset
`,
{
replacements: {
context: context || '',
limit,
offset,
tagName,
},
type: QueryTypes.SELECT,
}
);
{
replacements: {
context: context || '',
limit,
offset,
tagName,
},
type: QueryTypes.SELECT,
}
);
const values: PostJSON[] = [];
const values: PostJSON[] = [];
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
for (let r of result) {
const post = inflateResultToPostJSON(r);
values.push(post);
}
return values;
};
return values;
};
return {
model,
getPostsByTag,
addTagPost,
removeTagPost,
};
return {
model,
getPostsByTag,
addTagPost,
removeTagPost,
};
};
export default tags;
@@ -162,11 +162,11 @@ const selectTagPostsQuery = `
LEFT JOIN moderations modliked ON modliked."messageId" = (SELECT "messageId" FROM moderations WHERE subtype = 'LIKE' AND reference = p."messageId" AND creator = root.creator LIMIT 1)
LEFT JOIN moderations modblocked ON modblocked."messageId" = (SELECT "messageId" FROM moderations WHERE subtype = 'BLOCK' AND reference = p."messageId" AND creator = root.creator LIMIT 1)
LEFT JOIN moderations gmodblocked ON gmodblocked."messageId" = (SELECT "messageId" FROM moderations WHERE subtype = 'BLOCK' AND reference = p."messageId" AND creator IN (${config.moderators
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
LEFT JOIN connections gmodblockeduser ON gmodblockeduser."messageId" = (SELECT "messageId" FROM connections WHERE subtype = 'BLOCK' AND name = p."creator" AND creator IN (${config.moderators
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
.map(d => `'${d}'`)
.join(',')}) LIMIT 1)
LEFT JOIN connections modblockeduser ON modblockeduser."messageId" = (SELECT "messageId" FROM connections WHERE subtype = 'BLOCK' AND name = p."creator" AND creator = root.creator LIMIT 1)
LEFT JOIN connections modfolloweduser ON modfolloweduser."messageId" = (SELECT "messageId" FROM connections WHERE subtype = 'FOLLOW' AND name = p."creator" AND creator = root.creator LIMIT 1)
LEFT JOIN posts rp ON rp."messageId" = (SELECT "messageId" from posts WHERE p."messageId" = reference AND creator = :context AND subtype = 'REPOST' LIMIT 1)

View File

@@ -2,60 +2,60 @@ import { Sequelize, BIGINT, STRING, QueryTypes } from 'sequelize';
import { Mutex } from 'async-mutex';
type ThreadModel = {
message_id: string;
root_id: string;
message_id: string;
root_id: string;
};
const mutex = new Mutex();
const threads = (sequelize: Sequelize) => {
const model = sequelize.define(
'threads',
{
message_id: {
type: STRING,
},
root_id: {
type: STRING,
},
},
{
indexes: [{ fields: ['root_id', 'message_id'], unique: true }],
}
);
const model = sequelize.define(
'threads',
{
message_id: {
type: STRING,
},
root_id: {
type: STRING,
},
},
{
indexes: [{ fields: ['root_id', 'message_id'], unique: true }],
}
);
const addThreadData = async (rootId: string, messageId: string) => {
return mutex.runExclusive(async () => {
const res = await model.create({
root_id: rootId,
message_id: messageId,
});
const addThreadData = async (rootId: string, messageId: string) => {
return mutex.runExclusive(async () => {
const res = await model.create({
root_id: rootId,
message_id: messageId,
});
return res;
return res;
});
};
const removeThreadData = async (rootId: string, messageId: string) => {
return mutex.runExclusive(async () => {
try {
const res = await model.destroy({
where: {
root_id: rootId,
message_id: messageId,
},
});
};
return res;
} catch (e) {
return false;
}
});
};
const removeThreadData = async (rootId: string, messageId: string) => {
return mutex.runExclusive(async () => {
try {
const res = await model.destroy({
where: {
root_id: rootId,
message_id: messageId,
},
});
return res;
} catch (e) {
return false;
}
});
};
return {
model,
addThreadData,
removeThreadData,
};
return {
model,
addThreadData,
removeThreadData,
};
};
export default threads;

View File

@@ -3,149 +3,149 @@ import { Mutex } from 'async-mutex';
const mutex = new Mutex();
type TwitterAuthModel = {
userToken: string;
userTokenSecret: string;
userName: string;
userId: string;
userToken: string;
userTokenSecret: string;
userName: string;
userId: string;
};
const twitterAuth = (sequelize: Sequelize) => {
const model = sequelize.define(
'twitter_auth',
const model = sequelize.define(
'twitter_auth',
{
user_token: {
type: STRING,
},
user_token_secret: {
type: STRING,
},
username: {
type: STRING,
},
user_id: {
type: STRING,
},
account: {
type: STRING,
},
},
{
indexes: [
{
user_token: {
type: STRING,
},
user_token_secret: {
type: STRING,
},
username: {
type: STRING,
},
user_id: {
type: STRING,
},
account: {
type: STRING,
},
unique: true,
fields: ['user_token'],
},
{
indexes: [
{
unique: true,
fields: ['user_token'],
},
{
unique: true,
fields: ['username'],
},
{
unique: true,
fields: ['user_id'],
},
{
unique: true,
fields: ['account'],
},
],
}
);
unique: true,
fields: ['username'],
},
{
unique: true,
fields: ['user_id'],
},
{
unique: true,
fields: ['account'],
},
],
}
);
const findUserByToken = async (token?: string | null): Promise<TwitterAuthModel | null> => {
if (!token) return null;
const findUserByToken = async (token?: string | null): Promise<TwitterAuthModel | null> => {
if (!token) return null;
const result = await model.findOne({
where: {
user_token: token,
},
const result = await model.findOne({
where: {
user_token: token,
},
});
return result?.toJSON() as TwitterAuthModel;
};
const findUserByUsername = async (username: string): Promise<TwitterAuthModel | null> => {
if (!username) return null;
const result = await model.findOne({
where: {
username: username,
},
});
return result?.toJSON() as TwitterAuthModel;
};
const findUserByAccount = async (account: string): Promise<TwitterAuthModel | null> => {
if (!account) return null;
const result = await model.findOne({
where: {
account: account,
},
});
return result?.toJSON() as TwitterAuthModel;
};
const addAccount = async (username: string, account: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
username: username,
},
});
if (result) {
const json: any = await result?.toJSON();
if (json.username !== username) throw new Error(`${username} already exists`);
return result.update({
account,
});
}
return result?.toJSON() as TwitterAuthModel;
};
return model.create({
account,
username,
});
});
};
const findUserByUsername = async (username: string): Promise<TwitterAuthModel | null> => {
if (!username) return null;
const updateUserToken = async (data: TwitterAuthModel) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
username: data.userName,
},
});
const result = await model.findOne({
where: {
username: username,
},
if (result) {
return result.update({
user_token: data.userToken,
user_token_secret: data.userTokenSecret,
username: data.userName,
user_id: data.userId,
});
}
return result?.toJSON() as TwitterAuthModel;
};
return model.create({
user_token: data.userToken,
user_token_secret: data.userTokenSecret,
username: data.userName,
user_id: data.userId,
});
});
};
const findUserByAccount = async (account: string): Promise<TwitterAuthModel | null> => {
if (!account) return null;
const result = await model.findOne({
where: {
account: account,
},
});
return result?.toJSON() as TwitterAuthModel;
};
const addAccount = async (username: string, account: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
username: username,
},
});
if (result) {
const json: any = await result?.toJSON();
if (json.username !== username) throw new Error(`${username} already exists`);
return result.update({
account,
});
}
return model.create({
account,
username,
});
});
};
const updateUserToken = async (data: TwitterAuthModel) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
username: data.userName,
},
});
if (result) {
return result.update({
user_token: data.userToken,
user_token_secret: data.userTokenSecret,
username: data.userName,
user_id: data.userId,
});
}
return model.create({
user_token: data.userToken,
user_token_secret: data.userTokenSecret,
username: data.userName,
user_id: data.userId,
});
});
};
return {
model,
addAccount,
findUserByToken,
findUserByAccount,
findUserByUsername,
updateUserToken,
};
return {
model,
addAccount,
findUserByToken,
findUserByAccount,
findUserByUsername,
updateUserToken,
};
};
export default twitterAuth;

View File

@@ -2,86 +2,86 @@ import { Mutex } from 'async-mutex';
import { BIGINT, ENUM, QueryTypes, Sequelize, STRING } from 'sequelize';
export type UploadModel = {
cid: string;
filename: string;
username: string;
size: number;
mimetype: string;
cid: string;
filename: string;
username: string;
size: number;
mimetype: string;
};
const mutex = new Mutex();
const uploads = (sequelize: Sequelize) => {
const model = sequelize.define(
'uploads',
{
cid: {
type: STRING,
},
filename: {
type: STRING,
},
username: {
type: STRING,
},
size: {
type: BIGINT,
},
mimetype: {
type: STRING,
},
},
{
indexes: [
{ fields: ['cid'] },
{ fields: ['filename'] },
{ fields: ['username'] },
{ fields: ['mimetype'] },
],
}
);
const model = sequelize.define(
'uploads',
{
cid: {
type: STRING,
},
filename: {
type: STRING,
},
username: {
type: STRING,
},
size: {
type: BIGINT,
},
mimetype: {
type: STRING,
},
},
{
indexes: [
{ fields: ['cid'] },
{ fields: ['filename'] },
{ fields: ['username'] },
{ fields: ['mimetype'] },
],
}
);
const addUploadData = async (data: UploadModel) => {
return mutex.runExclusive(async () => {
const res = await model.create(data);
return res;
const addUploadData = async (data: UploadModel) => {
return mutex.runExclusive(async () => {
const res = await model.create(data);
return res;
});
};
const removeUploadData = async (cid: string, filename: string) => {
return mutex.runExclusive(async () => {
try {
const res = await model.destroy({
where: {
cid,
filename,
},
});
};
const removeUploadData = async (cid: string, filename: string) => {
return mutex.runExclusive(async () => {
try {
const res = await model.destroy({
where: {
cid,
filename,
},
});
return res;
} catch (e) {
return false;
}
});
};
const getTotalUploadByUser = async (username: string) => {
const res = await model.sum('size', {
where: {
username,
},
});
if (isNaN(res)) return 0;
return res;
};
} catch (e) {
return false;
}
});
};
return {
model,
addUploadData,
removeUploadData,
getTotalUploadByUser,
};
const getTotalUploadByUser = async (username: string) => {
const res = await model.sum('size', {
where: {
username,
},
});
if (isNaN(res)) return 0;
return res;
};
return {
model,
addUploadData,
removeUploadData,
getTotalUploadByUser,
};
};
export default uploads;

View File

@@ -2,121 +2,121 @@ import { BIGINT, Sequelize, STRING } from 'sequelize';
import { Mutex } from 'async-mutex';
type UserMetaModel = {
name: string;
followerCount: number;
followingCount: number;
blockedCount: number;
blockingCount: number;
mentionedCount: number;
postingCount: number;
name: string;
followerCount: number;
followingCount: number;
blockedCount: number;
blockingCount: number;
mentionedCount: number;
postingCount: number;
};
const mutex = new Mutex();
const emptyMeta = {
followerCount: 0,
followingCount: 0,
blockedCount: 0,
blockingCount: 0,
mentionedCount: 0,
postingCount: 0,
followerCount: 0,
followingCount: 0,
blockedCount: 0,
blockingCount: 0,
mentionedCount: 0,
postingCount: 0,
};
const userMeta = (sequelize: Sequelize) => {
const model = sequelize.define(
'usermeta',
{
name: {
type: STRING,
allowNull: false,
primaryKey: true,
},
followerCount: {
type: BIGINT,
},
followingCount: {
type: BIGINT,
},
blockedCount: {
type: BIGINT,
},
blockingCount: {
type: BIGINT,
},
mentionedCount: {
type: BIGINT,
},
postingCount: {
type: BIGINT,
},
},
{
indexes: [{ fields: ['name'], unique: true }],
}
);
const model = sequelize.define(
'usermeta',
{
name: {
type: STRING,
allowNull: false,
primaryKey: true,
},
followerCount: {
type: BIGINT,
},
followingCount: {
type: BIGINT,
},
blockedCount: {
type: BIGINT,
},
blockingCount: {
type: BIGINT,
},
mentionedCount: {
type: BIGINT,
},
postingCount: {
type: BIGINT,
},
},
{
indexes: [{ fields: ['name'], unique: true }],
}
);
const findOne = async (name: string): Promise<UserMetaModel | null> => {
let result = await model.findOne({
where: {
name,
},
const findOne = async (name: string): Promise<UserMetaModel | null> => {
let result = await model.findOne({
where: {
name,
},
});
return (
(result?.toJSON() as UserMetaModel) || {
...emptyMeta,
}
);
};
const update = async (record: UserMetaModel) => {
return model.create(record);
};
return {
model,
update,
findOne,
addFollower: makeKeyIncrementer('followerCount', 1),
addFollowing: makeKeyIncrementer('followingCount', 1),
addBlocked: makeKeyIncrementer('blockedCount', 1),
addBlocking: makeKeyIncrementer('blockingCount', 1),
addPostingCount: makeKeyIncrementer('postingCount', 1),
addMentionedCount: makeKeyIncrementer('mentionedCount', 1),
removeFollower: makeKeyIncrementer('followerCount', -1),
removeFollowing: makeKeyIncrementer('followingCount', -1),
removeBlocked: makeKeyIncrementer('blockedCount', -1),
removeBlocking: makeKeyIncrementer('blockingCount', -1),
removePostingCount: makeKeyIncrementer('postingCount', -1),
removeMentionedCount: makeKeyIncrementer('mentionedCount', -1),
};
function makeKeyIncrementer(key: string, delta: number) {
return async (name: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: { name },
});
return (
(result?.toJSON() as UserMetaModel) || {
...emptyMeta,
}
);
if (result) {
const data = result.toJSON() as UserMetaModel;
return result.update({
...data,
// @ts-ignore
[key]: Math.max(0, (Number(data[key]) || 0) + delta),
});
}
const res = await model.create({
name,
...emptyMeta,
[key]: Math.max(0, delta),
});
return res;
});
};
const update = async (record: UserMetaModel) => {
return model.create(record);
};
return {
model,
update,
findOne,
addFollower: makeKeyIncrementer('followerCount', 1),
addFollowing: makeKeyIncrementer('followingCount', 1),
addBlocked: makeKeyIncrementer('blockedCount', 1),
addBlocking: makeKeyIncrementer('blockingCount', 1),
addPostingCount: makeKeyIncrementer('postingCount', 1),
addMentionedCount: makeKeyIncrementer('mentionedCount', 1),
removeFollower: makeKeyIncrementer('followerCount', -1),
removeFollowing: makeKeyIncrementer('followingCount', -1),
removeBlocked: makeKeyIncrementer('blockedCount', -1),
removeBlocking: makeKeyIncrementer('blockingCount', -1),
removePostingCount: makeKeyIncrementer('postingCount', -1),
removeMentionedCount: makeKeyIncrementer('mentionedCount', -1),
};
function makeKeyIncrementer(key: string, delta: number) {
return async (name: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: { name },
});
if (result) {
const data = result.toJSON() as UserMetaModel;
return result.update({
...data,
// @ts-ignore
[key]: Math.max(0, (Number(data[key]) || 0) + delta),
});
}
const res = await model.create({
name,
...emptyMeta,
[key]: Math.max(0, delta),
});
return res;
});
};
}
}
};
export default userMeta;

View File

@@ -3,137 +3,137 @@ import userMetaSeq from './userMeta';
import { Mutex } from 'async-mutex';
export type UserModel = {
username: string;
type: 'ens' | 'arbitrum' | '';
pubkey: string;
joinedAt: number;
joinedTx: string;
name: string;
bio: string;
coverImage: string;
profileImage: string;
website: string;
twitterVerification: string;
group: boolean;
meta: {
inviteSent: string | null;
acceptanceReceived: string | null;
inviteReceived: string | null;
acceptanceSent: string | null;
blockedCount: number;
blockingCount: number;
followerCount: number;
followingCount: number;
postingCount: number;
mentionedCount: number;
followed: string | null;
blocked: string | null;
};
username: string;
type: 'ens' | 'arbitrum' | '';
pubkey: string;
joinedAt: number;
joinedTx: string;
name: string;
bio: string;
coverImage: string;
profileImage: string;
website: string;
twitterVerification: string;
group: boolean;
meta: {
inviteSent: string | null;
acceptanceReceived: string | null;
inviteReceived: string | null;
acceptanceSent: string | null;
blockedCount: number;
blockingCount: number;
followerCount: number;
followingCount: number;
postingCount: number;
mentionedCount: number;
followed: string | null;
blocked: string | null;
};
};
const mutex = new Mutex();
const users = (sequelize: Sequelize) => {
const model = sequelize.define(
'users',
{
name: {
type: STRING,
allowNull: false,
validate: {
notEmpty: true,
},
primaryKey: true,
unique: true,
},
pubkey: {
type: STRING,
allowNull: false,
},
joinedAt: {
type: BIGINT,
},
tx: {
type: STRING,
allowNull: false,
},
type: {
type: ENUM('arbitrum', 'ens', ''),
allowNull: false,
},
const model = sequelize.define(
'users',
{
name: {
type: STRING,
allowNull: false,
validate: {
notEmpty: true,
},
{
indexes: [
{ fields: ['name'] },
{ fields: ['pubkey'] },
{ fields: ['type'] },
{ fields: ['tx'] },
],
}
);
primaryKey: true,
unique: true,
},
pubkey: {
type: STRING,
allowNull: false,
},
joinedAt: {
type: BIGINT,
},
tx: {
type: STRING,
allowNull: false,
},
type: {
type: ENUM('arbitrum', 'ens', ''),
allowNull: false,
},
},
{
indexes: [
{ fields: ['name'] },
{ fields: ['pubkey'] },
{ fields: ['type'] },
{ fields: ['tx'] },
],
}
);
const findOneByName = async (name: string, context = ''): Promise<UserModel | null> => {
const values = await sequelize.query(
`
const findOneByName = async (name: string, context = ''): Promise<UserModel | null> => {
const values = await sequelize.query(
`
${userSelectQuery}
WHERE u.name = :name
`,
{
type: QueryTypes.SELECT,
replacements: {
context: context || '',
name: name,
},
}
);
{
type: QueryTypes.SELECT,
replacements: {
context: context || '',
name: name,
},
}
);
const [user] = inflateValuesToUserJSON(values);
const [user] = inflateValuesToUserJSON(values);
return user || null;
};
return user || null;
};
const findOneByPubkey = async (pubkey: string): Promise<UserModel | null> => {
let result = await model.findOne({
where: {
pubkey,
},
});
const findOneByPubkey = async (pubkey: string): Promise<UserModel | null> => {
let result = await model.findOne({
where: {
pubkey,
},
});
if (!result) return null;
if (!result) return null;
const json = result.toJSON() as UserModel;
const json = result.toJSON() as UserModel;
return json;
};
return json;
};
const readAll = async (context = '', offset = 0, limit = 20): Promise<UserModel[]> => {
const values = await sequelize.query(
`
const readAll = async (context = '', offset = 0, limit = 20): Promise<UserModel[]> => {
const values = await sequelize.query(
`
${userSelectQuery}
ORDER BY (umt."followerCount"+umt."postingCount"+umt."mentionedCount") ASC
LIMIT :limit OFFSET :offset
`,
{
type: QueryTypes.SELECT,
replacements: {
context: context || '',
limit,
offset,
},
}
);
{
type: QueryTypes.SELECT,
replacements: {
context: context || '',
limit,
offset,
},
}
);
return inflateValuesToUserJSON(values);
};
return inflateValuesToUserJSON(values);
};
const search = async (
query: string,
context = '',
offset = 0,
limit = 5
): Promise<UserModel[]> => {
const values = await sequelize.query(
`
const search = async (
query: string,
context = '',
offset = 0,
limit = 5
): Promise<UserModel[]> => {
const values = await sequelize.query(
`
${userSelectQuery}
WHERE (
LOWER(u."name") LIKE :query
@@ -142,75 +142,75 @@ const users = (sequelize: Sequelize) => {
)
LIMIT :limit OFFSET :offset
`,
{
type: QueryTypes.SELECT,
replacements: {
context: context || '',
query: `${query.toLowerCase()}%`,
limit,
offset,
},
}
);
{
type: QueryTypes.SELECT,
replacements: {
context: context || '',
query: `${query.toLowerCase()}%`,
limit,
offset,
},
}
);
return inflateValuesToUserJSON(values);
};
return inflateValuesToUserJSON(values);
};
const updateOrCreateUser = async (user: {
name: string;
pubkey: string;
joinedAt: number;
tx: string;
type: 'ens' | 'arbitrum';
}) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
name: user.name,
},
});
const updateOrCreateUser = async (user: {
name: string;
pubkey: string;
joinedAt: number;
tx: string;
type: 'ens' | 'arbitrum';
}) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
name: user.name,
},
});
if (result) {
const json = result.toJSON() as UserModel;
if (user.joinedAt > Number(json.joinedAt)) {
return result.update(user);
}
return;
}
if (result) {
const json = result.toJSON() as UserModel;
if (user.joinedAt > Number(json.joinedAt)) {
return result.update(user);
}
return;
}
return model.create(user);
return model.create(user);
});
};
const ensureUser = async (name: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
name: name,
},
});
if (!result) {
return model.create({
name,
tx: '',
type: '',
pubkey: '',
joined: 0,
});
};
}
});
};
const ensureUser = async (name: string) => {
return mutex.runExclusive(async () => {
const result = await model.findOne({
where: {
name: name,
},
});
if (!result) {
return model.create({
name,
tx: '',
type: '',
pubkey: '',
joined: 0,
});
}
});
};
return {
model,
ensureUser,
findOneByName,
findOneByPubkey,
readAll,
search,
updateOrCreateUser,
};
return {
model,
ensureUser,
findOneByName,
findOneByPubkey,
readAll,
search,
updateOrCreateUser,
};
};
export default users;
@@ -264,43 +264,43 @@ const userSelectQuery = `
`;
function inflateValuesToUserJSON(values: any[]): UserModel[] {
return values.map(value => {
let twitterVerification = '';
return values.map(value => {
let twitterVerification = '';
if (value.tweetId && value.twitterHandle) {
twitterVerification = `https://twitter.com/${value.twitterHandle}/status/${value.tweetId}`;
}
if (value.tweetId && value.twitterHandle) {
twitterVerification = `https://twitter.com/${value.twitterHandle}/status/${value.tweetId}`;
}
return {
username: value.name,
address: value.name,
joinedTx: value.tx,
type: value.type,
pubkey: value.pubkey,
joinedAt: Number(value.joinedAt),
name: value.nickname || '',
bio: value.bio || '',
profileImage: value.profileImage || '',
coverImage: value.coverImage || '',
group: !!value.group,
twitterVerification: twitterVerification,
website: value.website || '',
ecdh: value.ecdh || '',
idcommitment: value.idcommitment || '',
meta: {
inviteSent: value.invited || null,
acceptanceReceived: value.accepted || null,
inviteReceived: value.invrecv || null,
acceptanceSent: value.acceptsent || null,
blockedCount: value.blockedCount ? Number(value.blockedCount) : 0,
blockingCount: value.blockingCount ? Number(value.blockingCount) : 0,
followerCount: value.followerCount ? Number(value.followerCount) : 0,
followingCount: value.followingCount ? Number(value.followingCount) : 0,
postingCount: value.postingCount ? Number(value.postingCount) : 0,
mentionedCount: value.mentionedCount ? Number(value.mentionedCount) : 0,
followed: value.followed,
blocked: value.blocked,
},
};
});
return {
username: value.name,
address: value.name,
joinedTx: value.tx,
type: value.type,
pubkey: value.pubkey,
joinedAt: Number(value.joinedAt),
name: value.nickname || '',
bio: value.bio || '',
profileImage: value.profileImage || '',
coverImage: value.coverImage || '',
group: !!value.group,
twitterVerification: twitterVerification,
website: value.website || '',
ecdh: value.ecdh || '',
idcommitment: value.idcommitment || '',
meta: {
inviteSent: value.invited || null,
acceptanceReceived: value.accepted || null,
inviteReceived: value.invrecv || null,
acceptanceSent: value.acceptsent || null,
blockedCount: value.blockedCount ? Number(value.blockedCount) : 0,
blockingCount: value.blockingCount ? Number(value.blockingCount) : 0,
followerCount: value.followerCount ? Number(value.followerCount) : 0,
followingCount: value.followingCount ? Number(value.followingCount) : 0,
postingCount: value.postingCount ? Number(value.postingCount) : 0,
mentionedCount: value.mentionedCount ? Number(value.mentionedCount) : 0,
followed: value.followed,
blocked: value.blocked,
},
};
});
}

View File

@@ -4,69 +4,69 @@ import { stubCall } from '../util/testUtils';
import ArbitrumService from './arbitrum';
tape('ArbitrumService', async t => {
const arb = new ArbitrumService();
const [call, stubs] = stubCall(arb);
const arb = new ArbitrumService();
const [call, stubs] = stubCall(arb);
t.equal(
await arb.getNonce('0x5741cc1bDb03738Eaed6F227E435fc08e6bE157B'),
'1',
'get correct nonce'
);
t.equal(
await arb.getNonce('0x5741cc1bDb03738Eaed6F227E435fc08e6bE157B'),
'1',
'get correct nonce'
);
const updateFor = sinon.stub(arb.registrar.methods, 'updateFor').returns({ send: () => null });
const updateFor = sinon.stub(arb.registrar.methods, 'updateFor').returns({ send: () => null });
const events: any[] = [
{
transactionHash: '',
blockNumber: '',
returnValues: {
value: '0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e61616161616161616161616161616161616161616161616161616161616161616161616161616161616161',
account: '0xmyuser',
},
},
];
const events: any[] = [
{
transactionHash: '',
blockNumber: '',
returnValues: {
value:
'0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e61616161616161616161616161616161616161616161616161616161616161616161616161616161616161',
account: '0xmyuser',
},
},
];
await arb.updateFor(
'0x5741cc1bDb03738Eaed6F227E435fc08e6bE157B',
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'0x12345'
);
await arb.updateFor(
'0x5741cc1bDb03738Eaed6F227E435fc08e6bE157B',
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'0x12345'
);
t.deepEqual(
updateFor.args[0],
[
'0x5741cc1bDb03738Eaed6F227E435fc08e6bE157B',
'0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e61616161616161616161616161616161616161616161616161616161616161616161616161616161616161',
'0x12345',
],
'should send update with correct proof'
);
t.deepEqual(
updateFor.args[0],
[
'0x5741cc1bDb03738Eaed6F227E435fc08e6bE157B',
'0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161612e61616161616161616161616161616161616161616161616161616161616161616161616161616161616161',
'0x12345',
],
'should send update with correct proof'
);
stubs.app.read.returns(
Promise.resolve({
lastArbitrumBlockScanned: 2000000,
})
);
stubs.app.read.returns(
Promise.resolve({
lastArbitrumBlockScanned: 2000000,
})
);
sinon
.stub(arb.web3.eth, 'getTransaction')
.returns(Promise.resolve({ hash: '0xtxhash' } as any));
sinon.stub(arb.web3.eth, 'getBlock').returns(Promise.resolve({ timestamp: 1648624746 } as any));
sinon.stub(arb.registrar, 'getPastEvents').returns(Promise.resolve(events));
sinon.stub(arb.web3.eth, 'getTransaction').returns(Promise.resolve({ hash: '0xtxhash' } as any));
sinon.stub(arb.web3.eth, 'getBlock').returns(Promise.resolve({ timestamp: 1648624746 } as any));
sinon.stub(arb.registrar, 'getPastEvents').returns(Promise.resolve(events));
await arb.scanFromLast();
await arb.scanFromLast();
t.deepEqual(stubs.users.updateOrCreateUser.args, [
[
{
name: '0xmyuser',
pubkey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
joinedAt: 1648624746000,
tx: '0xtxhash',
type: 'arbitrum',
},
],
]);
t.deepEqual(stubs.users.updateOrCreateUser.args, [
[
{
name: '0xmyuser',
pubkey:
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
joinedAt: 1648624746000,
tx: '0xtxhash',
type: 'arbitrum',
},
],
]);
t.end();
t.end();
});

View File

@@ -7,126 +7,123 @@ import Timeout = NodeJS.Timeout;
import { arbRegistrarABI } from '../util/abi';
export default class ArbitrumService extends GenericService {
web3: Web3;
registrar: Contract;
scanTimeout?: Timeout | null;
web3: Web3;
registrar: Contract;
scanTimeout?: Timeout | null;
constructor() {
super();
const httpProvider = new Web3.providers.HttpProvider(config.arbitrumHttpProvider);
this.web3 = new Web3(httpProvider);
this.web3.eth.accounts.wallet.add(config.arbitrumPrivateKey);
this.registrar = new this.web3.eth.Contract(
arbRegistrarABI as any,
config.arbitrumRegistrar
);
}
constructor() {
super();
const httpProvider = new Web3.providers.HttpProvider(config.arbitrumHttpProvider);
this.web3 = new Web3(httpProvider);
this.web3.eth.accounts.wallet.add(config.arbitrumPrivateKey);
this.registrar = new this.web3.eth.Contract(arbRegistrarABI as any, config.arbitrumRegistrar);
}
getNonce = async (account: string): Promise<string> => {
return this.registrar.methods.nonces(account).call();
};
getNonce = async (account: string): Promise<string> => {
return this.registrar.methods.nonces(account).call();
};
updateFor = async (account: string, pubkey: string, proof: string) => {
const pubkeyBytes = this.web3.utils.utf8ToHex(pubkey);
return await this.registrar.methods.updateFor(account, pubkeyBytes, proof).send({
from: config.arbitrumAddress,
gas: '10000000',
});
};
updateFor = async (account: string, pubkey: string, proof: string) => {
const pubkeyBytes = this.web3.utils.utf8ToHex(pubkey);
return await this.registrar.methods.updateFor(account, pubkeyBytes, proof).send({
from: config.arbitrumAddress,
gas: '10000000',
});
};
async scanFromLast() {
const app = await this.call('db', 'getApp');
const data = await app.read();
const lastBlock = data?.lastArbitrumBlockScanned;
async scanFromLast() {
const app = await this.call('db', 'getApp');
const data = await app.read();
const lastBlock = data?.lastArbitrumBlockScanned;
logger.info('scanning Autism Registrar on arbitrum', {
logger.info('scanning Autism Registrar on arbitrum', {
fromBlock: lastBlock,
});
try {
const block = await this.web3.eth.getBlock('latest');
const toBlock = Math.min(block.number, lastBlock + 99999);
const events = await this.registrar.getPastEvents('RecordUpdatedFor', {
fromBlock: lastBlock,
toBlock: toBlock,
});
logger.info('scanned Autism Registrar on arbitrum', {
fromBlock: lastBlock,
toBlock: toBlock,
});
for (let event of events) {
const tx = await this.web3.eth.getTransaction(event.transactionHash);
const block = await this.web3.eth.getBlock(event.blockNumber);
const pubkeyBytes = event.returnValues.value;
const account = event.returnValues.account;
const pubkey = Web3.utils.hexToUtf8(pubkeyBytes);
const x = pubkey.split('.')[0];
const y = pubkey.split('.')[1];
if (x.length !== 43 || y.length !== 43) {
logger.error('invalid pubkey', {
fromBlock: lastBlock,
});
toBlock: block.number,
});
continue;
}
const users = await this.call('db', 'getUsers');
try {
const block = await this.web3.eth.getBlock('latest');
const toBlock = Math.min(block.number, lastBlock + 99999);
const events = await this.registrar.getPastEvents('RecordUpdatedFor', {
fromBlock: lastBlock,
toBlock: toBlock,
});
logger.info('scanned Autism Registrar on arbitrum', {
fromBlock: lastBlock,
toBlock: toBlock,
});
for (let event of events) {
const tx = await this.web3.eth.getTransaction(event.transactionHash);
const block = await this.web3.eth.getBlock(event.blockNumber);
const pubkeyBytes = event.returnValues.value;
const account = event.returnValues.account;
const pubkey = Web3.utils.hexToUtf8(pubkeyBytes);
const x = pubkey.split('.')[0];
const y = pubkey.split('.')[1];
if (x.length !== 43 || y.length !== 43) {
logger.error('invalid pubkey', {
fromBlock: lastBlock,
toBlock: block.number,
});
continue;
}
const users = await this.call('db', 'getUsers');
try {
await users.updateOrCreateUser({
name: account,
pubkey,
joinedAt: Number(block.timestamp) * 1000,
tx: tx.hash,
type: 'arbitrum',
});
} catch (e) {
logger.error(e.message, {
parent: e.parent,
stack: e.stack,
fromBlock: lastBlock,
});
}
await this.call('gun', 'watch', pubkey);
logger.info(`added pubkey for ${account}`, {
transactionHash: tx.hash,
blockNumber: tx.blockNumber,
name: account,
pubkey: pubkey,
fromBlock: lastBlock,
});
}
await app.updateLastArbitrumBlock(toBlock);
if (block.number > toBlock) return true;
await users.updateOrCreateUser({
name: account,
pubkey,
joinedAt: Number(block.timestamp) * 1000,
tx: tx.hash,
type: 'arbitrum',
});
} catch (e) {
logger.error(e.message, {
parent: e.parent,
stack: e.stack,
fromBlock: lastBlock,
});
}
}
scan = async () => {
const shouldScanAgain = await this.scanFromLast();
if (this.scanTimeout) {
clearTimeout(this.scanTimeout);
this.scanTimeout = null;
logger.error(e.message, {
parent: e.parent,
stack: e.stack,
fromBlock: lastBlock,
});
}
this.scanTimeout = setTimeout(this.scan, shouldScanAgain ? 0 : 15000);
};
await this.call('gun', 'watch', pubkey);
async start() {
this.scan();
logger.info(`added pubkey for ${account}`, {
transactionHash: tx.hash,
blockNumber: tx.blockNumber,
name: account,
pubkey: pubkey,
fromBlock: lastBlock,
});
}
await app.updateLastArbitrumBlock(toBlock);
if (block.number > toBlock) return true;
} catch (e) {
logger.error(e.message, {
parent: e.parent,
stack: e.stack,
fromBlock: lastBlock,
});
}
}
scan = async () => {
const shouldScanAgain = await this.scanFromLast();
if (this.scanTimeout) {
clearTimeout(this.scanTimeout);
this.scanTimeout = null;
}
this.scanTimeout = setTimeout(this.scan, shouldScanAgain ? 0 : 15000);
};
async start() {
this.scan();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,247 +22,247 @@ import uploads from '../../models/uploads';
import merkleRoot from '../../models/merkle_root';
export default class DBService extends GenericService {
sequelize: Sequelize;
sqlite: Sequelize;
sequelize: Sequelize;
sqlite: Sequelize;
app?: ReturnType<typeof app>;
ens?: ReturnType<typeof ens>;
linkPreview?: ReturnType<typeof linkPreview>;
users?: ReturnType<typeof users>;
records?: ReturnType<typeof records>;
posts?: ReturnType<typeof posts>;
profiles?: ReturnType<typeof profiles>;
moderations?: ReturnType<typeof moderations>;
connections?: ReturnType<typeof connections>;
tags?: ReturnType<typeof tags>;
semaphore?: ReturnType<typeof semaphore>;
meta?: ReturnType<typeof meta>;
userMeta?: ReturnType<typeof userMeta>;
twitterAuth?: ReturnType<typeof twitterAuth>;
interepGroups?: ReturnType<typeof interepGroups>;
semaphoreCreators?: ReturnType<typeof semaphoreCreators>;
threads?: ReturnType<typeof threads>;
uploads?: ReturnType<typeof uploads>;
merkleRoot?: ReturnType<typeof merkleRoot>;
app?: ReturnType<typeof app>;
ens?: ReturnType<typeof ens>;
linkPreview?: ReturnType<typeof linkPreview>;
users?: ReturnType<typeof users>;
records?: ReturnType<typeof records>;
posts?: ReturnType<typeof posts>;
profiles?: ReturnType<typeof profiles>;
moderations?: ReturnType<typeof moderations>;
connections?: ReturnType<typeof connections>;
tags?: ReturnType<typeof tags>;
semaphore?: ReturnType<typeof semaphore>;
meta?: ReturnType<typeof meta>;
userMeta?: ReturnType<typeof userMeta>;
twitterAuth?: ReturnType<typeof twitterAuth>;
interepGroups?: ReturnType<typeof interepGroups>;
semaphoreCreators?: ReturnType<typeof semaphoreCreators>;
threads?: ReturnType<typeof threads>;
uploads?: ReturnType<typeof uploads>;
merkleRoot?: ReturnType<typeof merkleRoot>;
constructor() {
super();
constructor() {
super();
this.sqlite = new Sequelize({
dialect: 'sqlite',
storage: process.env.NODE_ENV === 'test' ? './gun.test.db' : './gun.db',
logging: false,
});
this.sqlite = new Sequelize({
dialect: 'sqlite',
storage: process.env.NODE_ENV === 'test' ? './gun.test.db' : './gun.db',
logging: false,
});
if (!config.dbDialect || config.dbDialect === 'sqlite') {
this.sequelize = new Sequelize({
dialect: 'sqlite',
storage: config.dbStorage,
logging: false,
});
} else {
this.sequelize = new Sequelize(
config.dbName as string,
config.dbUsername as string,
config.dbPassword,
{
host: config.dbHost,
port: Number(config.dbPort),
dialect: config.dbDialect as Dialect,
logging: false,
}
);
if (!config.dbDialect || config.dbDialect === 'sqlite') {
this.sequelize = new Sequelize({
dialect: 'sqlite',
storage: config.dbStorage,
logging: false,
});
} else {
this.sequelize = new Sequelize(
config.dbName as string,
config.dbUsername as string,
config.dbPassword,
{
host: config.dbHost,
port: Number(config.dbPort),
dialect: config.dbDialect as Dialect,
logging: false,
}
);
}
}
async getRecords(): Promise<ReturnType<typeof records>> {
if (!this.records) {
return Promise.reject(new Error('records is not initialized'));
}
return this.records;
}
async getUsers(): Promise<ReturnType<typeof users>> {
if (!this.users) {
return Promise.reject(new Error('users is not initialized'));
}
return this.users;
}
async getPosts(): Promise<ReturnType<typeof posts>> {
if (!this.posts) {
return Promise.reject(new Error('posts is not initialized'));
}
return this.posts;
}
async getConnections(): Promise<ReturnType<typeof connections>> {
if (!this.connections) {
return Promise.reject(new Error('connections is not initialized'));
}
return this.connections;
}
async getModerations(): Promise<ReturnType<typeof moderations>> {
if (!this.moderations) {
return Promise.reject(new Error('moderations is not initialized'));
}
return this.moderations;
}
async getProfiles(): Promise<ReturnType<typeof profiles>> {
if (!this.profiles) {
return Promise.reject(new Error('profiles is not initialized'));
}
async getRecords(): Promise<ReturnType<typeof records>> {
if (!this.records) {
return Promise.reject(new Error('records is not initialized'));
}
return this.records;
return this.profiles;
}
async getMeta(): Promise<ReturnType<typeof meta>> {
if (!this.meta) {
return Promise.reject(new Error('meta is not initialized'));
}
async getUsers(): Promise<ReturnType<typeof users>> {
if (!this.users) {
return Promise.reject(new Error('users is not initialized'));
}
return this.users;
return this.meta;
}
async getTags(): Promise<ReturnType<typeof tags>> {
if (!this.tags) {
return Promise.reject(new Error('tags is not initialized'));
}
async getPosts(): Promise<ReturnType<typeof posts>> {
if (!this.posts) {
return Promise.reject(new Error('posts is not initialized'));
}
return this.posts;
return this.tags;
}
async getUserMeta(): Promise<ReturnType<typeof userMeta>> {
if (!this.userMeta) {
return Promise.reject(new Error('userMeta is not initialized'));
}
async getConnections(): Promise<ReturnType<typeof connections>> {
if (!this.connections) {
return Promise.reject(new Error('connections is not initialized'));
}
return this.connections;
return this.userMeta;
}
async getTwitterAuth(): Promise<ReturnType<typeof twitterAuth>> {
if (!this.twitterAuth) {
return Promise.reject(new Error('twitterAuth is not initialized'));
}
async getModerations(): Promise<ReturnType<typeof moderations>> {
if (!this.moderations) {
return Promise.reject(new Error('moderations is not initialized'));
}
return this.moderations;
return this.twitterAuth;
}
async getApp(): Promise<ReturnType<typeof app>> {
if (!this.app) {
return Promise.reject(new Error('app is not initialized'));
}
return this.app;
}
async getENS(): Promise<ReturnType<typeof ens>> {
if (!this.ens) {
return Promise.reject(new Error('ens is not initialized'));
}
return this.ens;
}
async getLinkPreview(): Promise<ReturnType<typeof linkPreview>> {
if (!this.linkPreview) {
return Promise.reject(new Error('linkPreview is not initialized'));
}
return this.linkPreview;
}
async getSemaphore(): Promise<ReturnType<typeof semaphore>> {
if (!this.semaphore) {
return Promise.reject(new Error('semaphore is not initialized'));
}
return this.semaphore;
}
async getInterepGroups(): Promise<ReturnType<typeof interepGroups>> {
if (!this.interepGroups) {
return Promise.reject(new Error('interepGroups is not initialized'));
}
return this.interepGroups;
}
async getSemaphoreCreators(): Promise<ReturnType<typeof semaphoreCreators>> {
if (!this.semaphoreCreators) {
return Promise.reject(new Error('semaphoreCreators is not initialized'));
}
return this.semaphoreCreators;
}
async getThreads(): Promise<ReturnType<typeof threads>> {
if (!this.threads) {
return Promise.reject(new Error('threads is not initialized'));
}
return this.threads;
}
async getUploads(): Promise<ReturnType<typeof uploads>> {
if (!this.uploads) {
return Promise.reject(new Error('uploads is not initialized'));
}
return this.uploads;
}
async start() {
this.app = app(this.sqlite);
this.records = records(this.sqlite);
this.linkPreview = linkPreview(this.sequelize);
this.meta = meta(this.sequelize);
this.userMeta = userMeta(this.sequelize);
this.moderations = moderations(this.sequelize);
this.connections = connections(this.sequelize);
this.users = users(this.sequelize);
this.posts = posts(this.sequelize);
this.tags = tags(this.sequelize);
this.profiles = profiles(this.sequelize);
this.semaphore = semaphore(this.sequelize);
this.ens = ens(this.sequelize);
this.twitterAuth = twitterAuth(this.sequelize);
this.interepGroups = interepGroups(this.sequelize);
this.semaphoreCreators = semaphoreCreators(this.sequelize);
this.threads = threads(this.sequelize);
this.uploads = uploads(this.sequelize);
this.merkleRoot = merkleRoot(this.sequelize);
await this.app?.model.sync({ force: !!process.env.FORCE });
await this.linkPreview?.model.sync({ force: !!process.env.FORCE });
await this.records?.model.sync({ force: !!process.env.FORCE });
await this.semaphore?.model.sync({ force: !!process.env.FORCE });
await this.users?.model.sync({ force: !!process.env.FORCE });
await this.moderations?.model.sync({ force: !!process.env.FORCE });
await this.connections?.model.sync({ force: !!process.env.FORCE });
await this.profiles?.model.sync({ force: !!process.env.FORCE });
await this.posts?.model.sync({ force: !!process.env.FORCE });
await this.tags?.model.sync({ force: !!process.env.FORCE });
await this.userMeta?.model.sync({ force: !!process.env.FORCE });
await this.meta?.model.sync({ force: !!process.env.FORCE });
await this.ens?.model.sync({ force: !!process.env.FORCE });
await this.twitterAuth?.model.sync({ force: !!process.env.FORCE });
await this.interepGroups?.model.sync({ force: !!process.env.FORCE });
await this.semaphoreCreators?.model.sync({ force: !!process.env.FORCE });
await this.threads?.model.sync({ force: !!process.env.FORCE });
await this.uploads?.model.sync({ force: !!process.env.FORCE });
await this.merkleRoot?.model.sync({ force: !!process.env.FORCE });
const appData = await this.app?.read();
if (!appData) {
await this.app?.updateLastENSBlock(12957300);
await this.app?.updateLastInterrepBlock(28311377);
await this.app?.updateLastArbitrumBlock(2193241);
await this.app?.updateLastGroup42BlockScanned(7660170);
// await this.app?.updateLastArbitrumBlock(9317700);
}
async getProfiles(): Promise<ReturnType<typeof profiles>> {
if (!this.profiles) {
return Promise.reject(new Error('profiles is not initialized'));
}
await this.app?.updateLastGroup42BlockScanned(7660170);
return this.profiles;
}
async getMeta(): Promise<ReturnType<typeof meta>> {
if (!this.meta) {
return Promise.reject(new Error('meta is not initialized'));
}
return this.meta;
}
async getTags(): Promise<ReturnType<typeof tags>> {
if (!this.tags) {
return Promise.reject(new Error('tags is not initialized'));
}
return this.tags;
}
async getUserMeta(): Promise<ReturnType<typeof userMeta>> {
if (!this.userMeta) {
return Promise.reject(new Error('userMeta is not initialized'));
}
return this.userMeta;
}
async getTwitterAuth(): Promise<ReturnType<typeof twitterAuth>> {
if (!this.twitterAuth) {
return Promise.reject(new Error('twitterAuth is not initialized'));
}
return this.twitterAuth;
}
async getApp(): Promise<ReturnType<typeof app>> {
if (!this.app) {
return Promise.reject(new Error('app is not initialized'));
}
return this.app;
}
async getENS(): Promise<ReturnType<typeof ens>> {
if (!this.ens) {
return Promise.reject(new Error('ens is not initialized'));
}
return this.ens;
}
async getLinkPreview(): Promise<ReturnType<typeof linkPreview>> {
if (!this.linkPreview) {
return Promise.reject(new Error('linkPreview is not initialized'));
}
return this.linkPreview;
}
async getSemaphore(): Promise<ReturnType<typeof semaphore>> {
if (!this.semaphore) {
return Promise.reject(new Error('semaphore is not initialized'));
}
return this.semaphore;
}
async getInterepGroups(): Promise<ReturnType<typeof interepGroups>> {
if (!this.interepGroups) {
return Promise.reject(new Error('interepGroups is not initialized'));
}
return this.interepGroups;
}
async getSemaphoreCreators(): Promise<ReturnType<typeof semaphoreCreators>> {
if (!this.semaphoreCreators) {
return Promise.reject(new Error('semaphoreCreators is not initialized'));
}
return this.semaphoreCreators;
}
async getThreads(): Promise<ReturnType<typeof threads>> {
if (!this.threads) {
return Promise.reject(new Error('threads is not initialized'));
}
return this.threads;
}
async getUploads(): Promise<ReturnType<typeof uploads>> {
if (!this.uploads) {
return Promise.reject(new Error('uploads is not initialized'));
}
return this.uploads;
}
async start() {
this.app = app(this.sqlite);
this.records = records(this.sqlite);
this.linkPreview = linkPreview(this.sequelize);
this.meta = meta(this.sequelize);
this.userMeta = userMeta(this.sequelize);
this.moderations = moderations(this.sequelize);
this.connections = connections(this.sequelize);
this.users = users(this.sequelize);
this.posts = posts(this.sequelize);
this.tags = tags(this.sequelize);
this.profiles = profiles(this.sequelize);
this.semaphore = semaphore(this.sequelize);
this.ens = ens(this.sequelize);
this.twitterAuth = twitterAuth(this.sequelize);
this.interepGroups = interepGroups(this.sequelize);
this.semaphoreCreators = semaphoreCreators(this.sequelize);
this.threads = threads(this.sequelize);
this.uploads = uploads(this.sequelize);
this.merkleRoot = merkleRoot(this.sequelize);
await this.app?.model.sync({ force: !!process.env.FORCE });
await this.linkPreview?.model.sync({ force: !!process.env.FORCE });
await this.records?.model.sync({ force: !!process.env.FORCE });
await this.semaphore?.model.sync({ force: !!process.env.FORCE });
await this.users?.model.sync({ force: !!process.env.FORCE });
await this.moderations?.model.sync({ force: !!process.env.FORCE });
await this.connections?.model.sync({ force: !!process.env.FORCE });
await this.profiles?.model.sync({ force: !!process.env.FORCE });
await this.posts?.model.sync({ force: !!process.env.FORCE });
await this.tags?.model.sync({ force: !!process.env.FORCE });
await this.userMeta?.model.sync({ force: !!process.env.FORCE });
await this.meta?.model.sync({ force: !!process.env.FORCE });
await this.ens?.model.sync({ force: !!process.env.FORCE });
await this.twitterAuth?.model.sync({ force: !!process.env.FORCE });
await this.interepGroups?.model.sync({ force: !!process.env.FORCE });
await this.semaphoreCreators?.model.sync({ force: !!process.env.FORCE });
await this.threads?.model.sync({ force: !!process.env.FORCE });
await this.uploads?.model.sync({ force: !!process.env.FORCE });
await this.merkleRoot?.model.sync({ force: !!process.env.FORCE });
const appData = await this.app?.read();
if (!appData) {
await this.app?.updateLastENSBlock(12957300);
await this.app?.updateLastInterrepBlock(28311377);
await this.app?.updateLastArbitrumBlock(2193241);
await this.app?.updateLastGroup42BlockScanned(7660170);
// await this.app?.updateLastArbitrumBlock(9317700);
}
await this.app?.updateLastGroup42BlockScanned(7660170);
// await this.app?.updateLastArbitrumBlock(2193241);
// await this.app?.updateLastInterrepBlock(27202837);
}
// await this.app?.updateLastArbitrumBlock(2193241);
// await this.app?.updateLastInterrepBlock(27202837);
}
}

View File

@@ -4,39 +4,39 @@ import ENSService from './ens';
import { stubCall } from '../util/testUtils';
tape('ENSService', async t => {
const ens = new ENSService();
const [call, stubs] = stubCall(ens);
await ens.start();
const ens = new ENSService();
const [call, stubs] = stubCall(ens);
await ens.start();
t.equal(
await ens.fetchAddressByName('yagamilight.eth'),
'0xd44a82dD160217d46D754a03C8f841edF06EBE3c',
'should get address for yagamilight.eth'
);
t.equal(
await ens.fetchAddressByName('yagamilight.eth'),
'0xd44a82dD160217d46D754a03C8f841edF06EBE3c',
'should get address for yagamilight.eth'
);
t.equal(
await ens.fetchAddressByName('0xd44a82dD160217d46D754a03C8f841edF06EBE3c'),
'0xd44a82dD160217d46D754a03C8f841edF06EBE3c',
'should get address for 0xd44a82dD160217d46D754a03C8f841edF06EBE3c'
);
t.equal(
await ens.fetchAddressByName('0xd44a82dD160217d46D754a03C8f841edF06EBE3c'),
'0xd44a82dD160217d46D754a03C8f841edF06EBE3c',
'should get address for 0xd44a82dD160217d46D754a03C8f841edF06EBE3c'
);
t.equal(
await ens.fetchNameByAddress('0xd44a82dD160217d46D754a03C8f841edF06EBE3c'),
'yagamilight.eth',
'should get ens for 0xd44a82dD160217d46D754a03C8f841edF06EBE3c'
);
t.equal(
await ens.fetchNameByAddress('0xd44a82dD160217d46D754a03C8f841edF06EBE3c'),
'yagamilight.eth',
'should get ens for 0xd44a82dD160217d46D754a03C8f841edF06EBE3c'
);
t.equal(
await ens.fetchNameByAddress('0xd44a82dD160217d46D754a03C8f841edF06EBE3d'),
null,
'should get ens for 0xd44a82dD160217d46D754a03C8f841edF06EBE3d'
);
t.equal(
await ens.fetchNameByAddress('0xd44a82dD160217d46D754a03C8f841edF06EBE3d'),
null,
'should get ens for 0xd44a82dD160217d46D754a03C8f841edF06EBE3d'
);
t.deepEqual(
stubs.ens.update.args[0],
['yagamilight.eth', '0xd44a82dD160217d46D754a03C8f841edF06EBE3c'],
'shuld update ens database'
);
t.deepEqual(
stubs.ens.update.args[0],
['yagamilight.eth', '0xd44a82dD160217d46D754a03C8f841edF06EBE3c'],
'shuld update ens database'
);
t.end();
t.end();
});

View File

@@ -9,65 +9,65 @@ import Timeout = NodeJS.Timeout;
const { default: ENS, getEnsAddress } = require('@ensdomains/ensjs');
const cache = new LRU({
max: 1000,
maxAge: 60 * 60 * 1000,
max: 1000,
maxAge: 60 * 60 * 1000,
});
export default class ENSService extends GenericService {
web3: Web3;
resolver: Contract;
ens: typeof ENS;
scanTimeout?: Timeout | null;
web3: Web3;
resolver: Contract;
ens: typeof ENS;
scanTimeout?: Timeout | null;
constructor() {
super();
const httpProvider = new Web3.providers.HttpProvider(config.web3HttpProvider);
this.web3 = new Web3(httpProvider);
this.resolver = new this.web3.eth.Contract(ensResolverABI as any, config.ensResolver);
this.ens = new ENS({
provider: httpProvider,
ensAddress: getEnsAddress('1'),
});
constructor() {
super();
const httpProvider = new Web3.providers.HttpProvider(config.web3HttpProvider);
this.web3 = new Web3(httpProvider);
this.resolver = new this.web3.eth.Contract(ensResolverABI as any, config.ensResolver);
this.ens = new ENS({
provider: httpProvider,
ensAddress: getEnsAddress('1'),
});
}
ecrecover = async (data: string, sig: string) => {
return this.web3.eth.accounts.recover(data, sig);
};
fetchNameByAddress = async (address: string) => {
const cached = cache.get(address);
if (cache.get(address)) {
return cached;
}
ecrecover = async (data: string, sig: string) => {
return this.web3.eth.accounts.recover(data, sig);
};
const { name } = await this.ens.getName(address);
fetchNameByAddress = async (address: string) => {
const cached = cache.get(address);
if (!name) return null;
if (cache.get(address)) {
return cached;
}
cache.set(address, name);
const ens = await this.call('db', 'getENS');
await ens.update(name, address);
return name;
};
const { name } = await this.ens.getName(address);
fetchAddressByName = async (name: string) => {
if (Web3.utils.isAddress(name)) return name;
if (!name) return null;
const cached = cache.get(name);
cache.set(address, name);
const ens = await this.call('db', 'getENS');
await ens.update(name, address);
return name;
};
if (cache.get(name)) {
return cached;
}
fetchAddressByName = async (name: string) => {
if (Web3.utils.isAddress(name)) return name;
const address = await this.ens.name(name).getAddress();
const cached = cache.get(name);
if (!address) throw new Error(`cannot find address for ${name}`);
if (cache.get(name)) {
return cached;
}
cache.set(name, address);
const ens = await this.call('db', 'getENS');
await ens.update(name, address);
const address = await this.ens.name(name).getAddress();
if (!address) throw new Error(`cannot find address for ${name}`);
cache.set(name, address);
const ens = await this.call('db', 'getENS');
await ens.update(name, address);
return address;
};
return address;
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -11,69 +11,69 @@ const interrep = new InterrepService();
const [callStub, { sempahore, interepGroups }] = stubCall(interrep);
tape('InterepService.start', async (t: any) => {
const datas = [
[
{
provider: 'twitter',
name: 'gold',
depth: 20,
root: '19217088683336594659449020493828377907203207941212636669271704950158751593251',
numberOfLeaves: 0,
size: 0,
},
{
provider: 'poap',
name: 'devcon5',
depth: 20,
root: '19217088683336594659449020493828377907203207941212636669271704950158751593251',
numberOfLeaves: 0,
size: 0,
},
{
provider: 'telegram',
name: '-1001396261340',
depth: 20,
root: '19217088683336594659449020493828377907203207941212636669271704950158751593251',
numberOfLeaves: 0,
size: 0,
},
{
provider: 'email',
name: 'hotmail',
depth: 20,
root: '19217088683336594659449020493828377907203207941212636669271704950158751593251',
numberOfLeaves: 0,
size: 0,
},
],
];
fetchStub.returns(
Promise.resolve({
json: async () => {
return {
data: datas.shift() || [],
};
},
})
);
const datas = [
[
{
provider: 'twitter',
name: 'gold',
depth: 20,
root: '19217088683336594659449020493828377907203207941212636669271704950158751593251',
numberOfLeaves: 0,
size: 0,
},
{
provider: 'poap',
name: 'devcon5',
depth: 20,
root: '19217088683336594659449020493828377907203207941212636669271704950158751593251',
numberOfLeaves: 0,
size: 0,
},
{
provider: 'telegram',
name: '-1001396261340',
depth: 20,
root: '19217088683336594659449020493828377907203207941212636669271704950158751593251',
numberOfLeaves: 0,
size: 0,
},
{
provider: 'email',
name: 'hotmail',
depth: 20,
root: '19217088683336594659449020493828377907203207941212636669271704950158751593251',
numberOfLeaves: 0,
size: 0,
},
],
];
fetchStub.returns(
Promise.resolve({
json: async () => {
return {
data: datas.shift() || [],
};
},
})
);
await interrep.start();
await interrep.start();
t.equal(
fetchStub.args[0][0],
'https://kovan.interep.link/api/v1/groups',
'should fetch groups data from interep API on start'
);
t.equal(
fetchStub.args[0][0],
'https://kovan.interep.link/api/v1/groups',
'should fetch groups data from interep API on start'
);
t.equal(
fetchStub.args[1][0],
'https://kovan.interep.link/api/v1/groups/twitter/gold/members?limit=1000&offset=0',
'should fetch groups membership data from interep API on start'
);
t.equal(
fetchStub.args[1][0],
'https://kovan.interep.link/api/v1/groups/twitter/gold/members?limit=1000&offset=0',
'should fetch groups membership data from interep API on start'
);
fetchStub.reset();
fetchStub.reset();
t.end();
t.end();
});
// tape('InterepService.addId', async t => {
@@ -102,89 +102,89 @@ tape('InterepService.start', async (t: any) => {
// });
tape('InterepService.getBatchFromRootHash', async t => {
fetchStub.returns(
Promise.resolve({
json: async () => ({
data: {
group: {
provider: 'autism',
name: 'diamond',
},
},
}),
})
);
const res = await interrep.getBatchFromRootHash('0x123456');
t.same(interepGroups.findOneByHash.args[0], ['0x123456']);
console.log(fetchStub.args[1]);
t.same(fetchStub.args[0][0], 'https://kovan.interep.link/api/v1/batches/0x123456');
t.same(
res,
{
fetchStub.returns(
Promise.resolve({
json: async () => ({
data: {
group: {
provider: 'autism',
name: 'diamond',
root_hash: '0x123456',
},
},
'should return batch from interep'
);
}),
})
);
fetchStub.reset();
interepGroups.findOneByHash.reset();
const res = await interrep.getBatchFromRootHash('0x123456');
t.end();
t.same(interepGroups.findOneByHash.args[0], ['0x123456']);
console.log(fetchStub.args[1]);
t.same(fetchStub.args[0][0], 'https://kovan.interep.link/api/v1/batches/0x123456');
t.same(
res,
{
provider: 'autism',
name: 'diamond',
root_hash: '0x123456',
},
'should return batch from interep'
);
fetchStub.reset();
interepGroups.findOneByHash.reset();
t.end();
});
tape('InterepService.getProofFromGroup', async t => {
fetchStub.returns(
Promise.resolve({
json: async () => ({
data: 'apple',
}),
})
);
fetchStub.returns(
Promise.resolve({
json: async () => ({
data: 'apple',
}),
})
);
const res = await interrep.getProofFromGroup('0x123456', 'autism', 'diamond');
const res = await interrep.getProofFromGroup('0x123456', 'autism', 'diamond');
t.same(
fetchStub.args[0][0],
'https://kovan.interep.link/api/v1/groups/0x123456/autism/diamond/proof'
);
t.same(
fetchStub.args[0][0],
'https://kovan.interep.link/api/v1/groups/0x123456/autism/diamond/proof'
);
t.same(
res,
{
data: 'apple',
},
'should return batch from interep'
);
t.same(
res,
{
data: 'apple',
},
'should return batch from interep'
);
fetchStub.reset();
fetchStub.reset();
t.end();
t.end();
});
tape('InterepService.inProvider', async t => {
fetchStub.returns(
Promise.resolve({
json: async () => ({
data: 'apple',
}),
})
);
fetchStub.returns(
Promise.resolve({
json: async () => ({
data: 'apple',
}),
})
);
const res = await interrep.inProvider('autism', '0x123456');
const res = await interrep.inProvider('autism', '0x123456');
t.same(fetchStub.args[0][0], 'https://kovan.interep.link/api/v1/providers/autism/0x123456');
t.same(fetchStub.args[0][0], 'https://kovan.interep.link/api/v1/providers/autism/0x123456');
t.equal(res, true);
t.equal(res, true);
fetchStub.reset();
fetchStub.reset();
t.end();
t.end();
});
// tape('InterepService.scanIDCommitment', async t => {

View File

@@ -5,155 +5,155 @@ import { sequelize } from '../util/sequelize';
import semaphore from '../models/semaphore';
export type InterepGroup = {
provider: 'twitter' | 'github' | 'reddit';
name: string;
root: string;
size: number;
provider: 'twitter' | 'github' | 'reddit';
name: string;
root: string;
size: number;
};
const INTEREP_SYNC_INTERVAL = 15 * 60 * 1000;
export default class InterrepService extends GenericService {
interepGroups?: ReturnType<typeof interepGroups>;
semaphore?: ReturnType<typeof semaphore>;
interepGroups?: ReturnType<typeof interepGroups>;
semaphore?: ReturnType<typeof semaphore>;
groups: {
[providerName: string]: InterepGroup[];
};
groups: {
[providerName: string]: InterepGroup[];
};
providers = ['twitter', 'github', 'reddit'];
providers = ['twitter', 'github', 'reddit'];
timeout: any;
timeout: any;
constructor() {
super();
this.groups = {};
constructor() {
super();
this.groups = {};
}
syncOne = async (group: string) => {
const [protocol, groupType, groupName] = group.split('_');
// @ts-ignore
const resp = await fetch(`${config.interrepAPI}/api/v1/groups/${groupType}/${groupName}`);
const json = await resp.json();
const data = json.data;
const existing = await this.interepGroups!.getGroup(groupType, groupName);
if (existing?.root_hash !== data.root) {
await this.fetchMembersFromGroup(data.root, groupType, groupName);
await this.interepGroups?.addHash(data.root, groupType, groupName);
}
};
sync = async () => {
try {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
// @ts-ignore
const resp = await fetch(`${config.interrepAPI}/api/v1/groups`);
const json = await resp.json();
const groups = json.data;
if (groups?.length) {
for (let group of groups) {
const { root, provider, name } = group;
const existing = await this.interepGroups!.getGroup(provider, name);
if (existing?.root_hash !== root) {
await this.fetchMembersFromGroup(root, provider, name);
await this.interepGroups?.addHash(root, provider, name);
}
}
}
} catch (e) {
console.log(e);
} finally {
this.timeout = setTimeout(this.sync, INTEREP_SYNC_INTERVAL);
}
};
async fetchMembersFromGroup(
root: string,
provider: string,
name: string,
limit = 1000,
offset = 0
): Promise<void> {
const groupId = `interrep_${provider}_${name}`;
// @ts-ignore
const resp = await fetch(
`${config.interrepAPI}/api/v1/groups/${provider}/${name}/members?limit=${limit}&offset=${offset}`
);
const json = await resp.json();
const members: string[] = json.data;
if (members.length) {
for (const member of members) {
await this.semaphore?.addID(BigInt(member).toString(16), groupId, root);
}
}
syncOne = async (group: string) => {
const [protocol, groupType, groupName] = group.split('_');
// @ts-ignore
const resp = await fetch(`${config.interrepAPI}/api/v1/groups/${groupType}/${groupName}`);
const json = await resp.json();
const data = json.data;
const existing = await this.interepGroups!.getGroup(groupType, groupName);
if (existing?.root_hash !== data.root) {
await this.fetchMembersFromGroup(data.root, groupType, groupName);
await this.interepGroups?.addHash(data.root, groupType, groupName);
}
};
if (members.length >= limit) {
await this.fetchMembersFromGroup(root, provider, name, limit + 1000, limit);
}
}
sync = async () => {
try {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
// @ts-ignore
const resp = await fetch(`${config.interrepAPI}/api/v1/groups`);
const json = await resp.json();
const groups = json.data;
if (groups?.length) {
for (let group of groups) {
const { root, provider, name } = group;
const existing = await this.interepGroups!.getGroup(provider, name);
if (existing?.root_hash !== root) {
await this.fetchMembersFromGroup(root, provider, name);
await this.interepGroups?.addHash(root, provider, name);
}
}
}
} catch (e) {
console.log(e);
} finally {
this.timeout = setTimeout(this.sync, INTEREP_SYNC_INTERVAL);
}
};
async getBatchFromRootHash(rootHash: string) {
try {
const interepGroups = await this.call('db', 'getInterepGroups');
async fetchMembersFromGroup(
root: string,
provider: string,
name: string,
limit = 1000,
offset = 0
): Promise<void> {
const groupId = `interrep_${provider}_${name}`;
// @ts-ignore
const resp = await fetch(
`${config.interrepAPI}/api/v1/groups/${provider}/${name}/members?limit=${limit}&offset=${offset}`
);
const json = await resp.json();
const members: string[] = json.data;
if (members.length) {
for (const member of members) {
await this.semaphore?.addID(BigInt(member).toString(16), groupId, root);
}
}
const exist = await interepGroups.findOneByHash(rootHash);
if (members.length >= limit) {
await this.fetchMembersFromGroup(root, provider, name, limit + 1000, limit);
}
if (exist) return exist;
// @ts-ignore
const resp = await fetch(`${config.interrepAPI}/api/v1/batches/${rootHash}`);
const json = await resp.json();
const group = json?.data?.group;
if (group) {
await interepGroups.addHash(rootHash, group.provider, group.name);
}
return {
name: group.name,
provider: group.provider,
root_hash: rootHash,
};
} catch (e) {
return false;
}
}
async getProofFromGroup(provider: string, name: string, id: string) {
try {
// @ts-ignore
const resp = await fetch(
`${config.interrepAPI}/api/v1/groups/${provider}/${name}/${id}/proof`
);
const json = await resp.json();
return json;
} catch (e) {
return false;
}
}
async inProvider(provider: string, id: string): Promise<boolean> {
// @ts-ignore
const resp = await fetch(`${config.interrepAPI}/api/v1/providers/${provider}/${id}`);
const json = await resp.json();
if (json?.data) {
return !!json?.data;
}
async getBatchFromRootHash(rootHash: string) {
try {
const interepGroups = await this.call('db', 'getInterepGroups');
return false;
}
const exist = await interepGroups.findOneByHash(rootHash);
async start() {
this.interepGroups = await interepGroups(sequelize);
this.semaphore = await semaphore(sequelize);
await this.sync();
}
if (exist) return exist;
// @ts-ignore
const resp = await fetch(`${config.interrepAPI}/api/v1/batches/${rootHash}`);
const json = await resp.json();
const group = json?.data?.group;
if (group) {
await interepGroups.addHash(rootHash, group.provider, group.name);
}
return {
name: group.name,
provider: group.provider,
root_hash: rootHash,
};
} catch (e) {
return false;
}
}
async getProofFromGroup(provider: string, name: string, id: string) {
try {
// @ts-ignore
const resp = await fetch(
`${config.interrepAPI}/api/v1/groups/${provider}/${name}/${id}/proof`
);
const json = await resp.json();
return json;
} catch (e) {
return false;
}
}
async inProvider(provider: string, id: string): Promise<boolean> {
// @ts-ignore
const resp = await fetch(`${config.interrepAPI}/api/v1/providers/${provider}/${id}`);
const json = await resp.json();
if (json?.data) {
return !!json?.data;
}
return false;
}
async start() {
this.interepGroups = await interepGroups(sequelize);
this.semaphore = await semaphore(sequelize);
await this.sync();
}
async stop() {
if (this.timeout) clearTimeout(this.timeout);
}
async stop() {
if (this.timeout) clearTimeout(this.timeout);
}
}

View File

@@ -3,21 +3,21 @@ import config from '../util/config';
import { CIDString, Filelike, Web3Storage } from 'web3.storage';
export default class IPFSService extends GenericService {
client: Web3Storage;
client: Web3Storage;
constructor() {
super();
// @ts-ignore
this.client = new Web3Storage({
token: config.web3StorageAPIKey as string,
});
}
constructor() {
super();
// @ts-ignore
this.client = new Web3Storage({
token: config.web3StorageAPIKey as string,
});
}
store = (files: Iterable<Filelike>): Promise<CIDString> => {
return this.client.put(files);
};
store = (files: Iterable<Filelike>): Promise<CIDString> => {
return this.client.put(files);
};
status = (cid: CIDString) => {
return this.client.status(cid);
};
status = (cid: CIDString) => {
return this.client.status(cid);
};
}

View File

@@ -7,170 +7,170 @@ import { sequelize } from '../util/sequelize';
import semaphore from '../models/semaphore';
export default class MerkleService extends GenericService {
merkleRoot: ReturnType<typeof merkleRoot>;
semaphore: ReturnType<typeof semaphore>;
merkleRoot: ReturnType<typeof merkleRoot>;
semaphore: ReturnType<typeof semaphore>;
constructor() {
super();
this.merkleRoot = merkleRoot(sequelize);
this.semaphore = semaphore(sequelize);
constructor() {
super();
this.merkleRoot = merkleRoot(sequelize);
this.semaphore = semaphore(sequelize);
}
getAllLeaves = async (group: string): Promise<any[]> => {
const [protocol, groupName, groupType = ''] = group.split('_');
const protocolBucket = SQL[protocol] || {};
const groupBucket = protocolBucket[groupName] || {};
const { sql, replacement } = groupBucket[groupType] || {};
let query = '';
const options: QueryOptions = { type: QueryTypes.SELECT };
if (protocol === 'custom') {
query = customGroupSQL;
options.replacements = {
group_address: groupName,
};
} else {
query = sql;
options.replacements = replacement || {
group_id: group,
};
}
getAllLeaves = async (group: string): Promise<any[]> => {
const [protocol, groupName, groupType = ''] = group.split('_');
const protocolBucket = SQL[protocol] || {};
const groupBucket = protocolBucket[groupName] || {};
const { sql, replacement } = groupBucket[groupType] || {};
let query = '';
const options: QueryOptions = { type: QueryTypes.SELECT };
if (!query) throw new Error(`${group} does not exist`);
if (protocol === 'custom') {
query = customGroupSQL;
options.replacements = {
group_address: groupName,
};
} else {
query = sql;
options.replacements = replacement || {
group_id: group,
};
}
const leaves = await sequelize.query(query, options);
return leaves;
};
if (!query) throw new Error(`${group} does not exist`);
makeTree = async (
group: string,
zkType: 'rln' | 'semaphore' = 'rln'
): Promise<IncrementalMerkleTree> => {
const [protocol, groupName, groupType = ''] = group.split('_');
const protocolBucket = SQL[protocol] || {};
const groupBucket = protocolBucket[groupName] || {};
const { sql, replacement } = groupBucket[groupType] || {};
let query = '';
const options: QueryOptions = { type: QueryTypes.SELECT };
const leaves = await sequelize.query(query, options);
return leaves;
if (protocol === 'custom') {
query = customGroupSQL;
options.replacements = {
group_address: groupName,
};
} else {
query = sql;
options.replacements = replacement || {
group_id: group,
};
}
if (!query) throw new Error(`${group} does not exist`);
const leaves = await sequelize.query(query, options);
const tree = generateMerkleTree(
zkType === 'rln' ? 15 : 20,
BigInt(0),
leaves.map(({ id_commitment }: any) => '0x' + id_commitment)
);
return tree;
};
getGroupByRoot = async (root: string): Promise<string | null> => {
const exist = await this.merkleRoot.getGroupByRoot(root);
return exist?.group_id || null;
};
verifyProof = async (proof: MerkleProof): Promise<string | null> => {
const groups = [
'zksocial_all',
'interrep_twitter_unrated',
'interrep_twitter_bronze',
'interrep_twitter_silver',
'interrep_twitter_gold',
];
const existingGroup = await this.getGroupByRoot(proof.root);
if (existingGroup) {
const tree = await this.makeTree(existingGroup);
if (tree.verifyProof(proof)) return existingGroup;
}
for (const group of groups) {
const tree = await this.makeTree(group);
if (tree.verifyProof(proof)) return group;
}
return null;
};
findProof = async (idCommitment: string, group?: string, _proofType?: 'semaphore' | 'rln') => {
if (!group) {
const row = await this.semaphore.findOneByCommitment(idCommitment);
if (!row) throw new Error(`${idCommitment} is not in any groups`);
group = row.group_id;
}
const exist = await this.semaphore.findOne(idCommitment, group);
const [protocol, service] = group.split('_');
if (!exist && protocol === 'interrep') {
await this.call('interrep', 'syncOne', group).catch(() => null);
}
const proofType = _proofType ? _proofType : service === 'taz' ? 'semaphore' : 'rln';
const tree = await this.makeTree(group, proofType);
const proof = await tree.createProof(tree.indexOf(BigInt('0x' + idCommitment)));
if (!proof) {
throw new Error(`${idCommitment} is not in ${group}`);
}
const root = '0x' + proof.root.toString(16);
await this.addRoot(root, group);
const retProof = {
root,
siblings: proof.siblings.map(siblings =>
Array.isArray(siblings)
? siblings.map(element => '0x' + element.toString(16))
: '0x' + siblings.toString(16)
),
pathIndices: proof.pathIndices,
leaf: '0x' + proof.leaf.toString(16),
group: group,
};
makeTree = async (
group: string,
zkType: 'rln' | 'semaphore' = 'rln'
): Promise<IncrementalMerkleTree> => {
const [protocol, groupName, groupType = ''] = group.split('_');
const protocolBucket = SQL[protocol] || {};
const groupBucket = protocolBucket[groupName] || {};
const { sql, replacement } = groupBucket[groupType] || {};
let query = '';
const options: QueryOptions = { type: QueryTypes.SELECT };
return retProof;
};
if (protocol === 'custom') {
query = customGroupSQL;
options.replacements = {
group_address: groupName,
};
} else {
query = sql;
options.replacements = replacement || {
group_id: group,
};
}
addRoot = async (rootHash: string, group: string) => {
return this.merkleRoot.addRoot(rootHash, group);
};
if (!query) throw new Error(`${group} does not exist`);
const leaves = await sequelize.query(query, options);
const tree = generateMerkleTree(
zkType === 'rln' ? 15 : 20,
BigInt(0),
leaves.map(({ id_commitment }: any) => '0x' + id_commitment)
);
return tree;
};
getGroupByRoot = async (root: string): Promise<string | null> => {
const exist = await this.merkleRoot.getGroupByRoot(root);
return exist?.group_id || null;
};
verifyProof = async (proof: MerkleProof): Promise<string | null> => {
const groups = [
'zksocial_all',
'interrep_twitter_unrated',
'interrep_twitter_bronze',
'interrep_twitter_silver',
'interrep_twitter_gold',
];
const existingGroup = await this.getGroupByRoot(proof.root);
if (existingGroup) {
const tree = await this.makeTree(existingGroup);
if (tree.verifyProof(proof)) return existingGroup;
}
for (const group of groups) {
const tree = await this.makeTree(group);
if (tree.verifyProof(proof)) return group;
}
return null;
};
findProof = async (idCommitment: string, group?: string, _proofType?: 'semaphore' | 'rln') => {
if (!group) {
const row = await this.semaphore.findOneByCommitment(idCommitment);
if (!row) throw new Error(`${idCommitment} is not in any groups`);
group = row.group_id;
}
const exist = await this.semaphore.findOne(idCommitment, group);
const [protocol, service] = group.split('_');
if (!exist && protocol === 'interrep') {
await this.call('interrep', 'syncOne', group).catch(() => null);
}
const proofType = _proofType ? _proofType : service === 'taz' ? 'semaphore' : 'rln';
const tree = await this.makeTree(group, proofType);
const proof = await tree.createProof(tree.indexOf(BigInt('0x' + idCommitment)));
if (!proof) {
throw new Error(`${idCommitment} is not in ${group}`);
}
const root = '0x' + proof.root.toString(16);
await this.addRoot(root, group);
const retProof = {
root,
siblings: proof.siblings.map(siblings =>
Array.isArray(siblings)
? siblings.map(element => '0x' + element.toString(16))
: '0x' + siblings.toString(16)
),
pathIndices: proof.pathIndices,
leaf: '0x' + proof.leaf.toString(16),
group: group,
};
return retProof;
};
addRoot = async (rootHash: string, group: string) => {
return this.merkleRoot.addRoot(rootHash, group);
};
findRoot = async (rootHash: string) => {
const cached = await this.merkleRoot.getGroupByRoot(rootHash);
return cached?.group_id;
};
findRoot = async (rootHash: string) => {
const cached = await this.merkleRoot.getGroupByRoot(rootHash);
return cached?.group_id;
};
}
const SQL: {
[protocol: string]: {
[groupName: string]: {
[groupType: string]: {
sql: string;
replacement?: BindOrReplacements;
};
};
[protocol: string]: {
[groupName: string]: {
[groupType: string]: {
sql: string;
replacement?: BindOrReplacements;
};
};
};
} = {
zksocial: {
all: {
'': {
sql: `
zksocial: {
all: {
'': {
sql: `
SELECT u.name as address, pf.value as id_commitment FROM users u
LEFT JOIN profiles pf ON pf."messageId" = (
SELECT "messageId" FROM profiles
@@ -180,34 +180,34 @@ const SQL: {
)
WHERE pf.value IS NOT NULL
`,
},
},
},
},
interrep: {
twitter: {
unrated: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
bronze: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
silver: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
gold: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
github: {
unrated: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
bronze: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
silver: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
gold: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
reddit: {
unrated: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
bronze: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
silver: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
gold: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
},
interrep: {
twitter: {
unrated: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
bronze: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
silver: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
gold: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
semaphore: {
taz: {
members: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
github: {
unrated: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
bronze: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
silver: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
gold: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
reddit: {
unrated: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
bronze: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
silver: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
gold: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
},
semaphore: {
taz: {
members: { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
},
};
export const customGroupSQL = `

View File

@@ -8,57 +8,53 @@ import { semaphoreABI } from '../../util/abi';
import { Contract } from 'web3-eth-contract';
export default class Group42 extends GenericService {
semaphore?: ReturnType<typeof semaphore>;
app: ReturnType<typeof app>;
web3: Web3;
contract: Contract;
semaphore?: ReturnType<typeof semaphore>;
app: ReturnType<typeof app>;
web3: Web3;
contract: Contract;
constructor(opts: { app: ReturnType<typeof app> }) {
super();
const httpProvider = new Web3.providers.HttpProvider(config.goerliHttpProvider);
this.web3 = new Web3(httpProvider);
this.contract = new this.web3.eth.Contract(
semaphoreABI as any,
'0xE585f0Db9aB24dC912404DFfb9b28fb8BF211fA6'
);
this.app = opts.app;
constructor(opts: { app: ReturnType<typeof app> }) {
super();
const httpProvider = new Web3.providers.HttpProvider(config.goerliHttpProvider);
this.web3 = new Web3(httpProvider);
this.contract = new this.web3.eth.Contract(
semaphoreABI as any,
'0xE585f0Db9aB24dC912404DFfb9b28fb8BF211fA6'
);
this.app = opts.app;
}
async start() {
this.semaphore = await semaphore(sequelize);
}
async sync() {
const data = await this.app.read();
const lastBlock = data?.lastGroup42BlockScanned;
const block = await this.web3.eth.getBlock('latest');
const toBlock = Math.min(block.number, lastBlock + 99999);
const events = await this.contract.getPastEvents('MemberAdded', {
fromBlock: lastBlock,
toBlock: toBlock,
});
for (const event of events) {
const identityCommitment = event.returnValues.identityCommitment;
const groupId = event.returnValues.groupId;
const merkleTreeRoot = event.returnValues.merkleTreeRoot;
const idCommitmentHex = BigInt(identityCommitment).toString(16);
const exist = await this.semaphore?.findOne(idCommitmentHex, 'semaphore_taz_members');
if (!exist && (groupId === '10806' || groupId === '42' || groupId === '10807')) {
await this.semaphore?.addID(idCommitmentHex, 'semaphore_taz_members', merkleTreeRoot);
}
}
async start() {
this.semaphore = await semaphore(sequelize);
}
async sync() {
const data = await this.app.read();
const lastBlock = data?.lastGroup42BlockScanned;
const block = await this.web3.eth.getBlock('latest');
const toBlock = Math.min(block.number, lastBlock + 99999);
const events = await this.contract.getPastEvents('MemberAdded', {
fromBlock: lastBlock,
toBlock: toBlock,
});
for (const event of events) {
const identityCommitment = event.returnValues.identityCommitment;
const groupId = event.returnValues.groupId;
const merkleTreeRoot = event.returnValues.merkleTreeRoot;
const idCommitmentHex = BigInt(identityCommitment).toString(16);
const exist = await this.semaphore?.findOne(idCommitmentHex, 'semaphore_taz_members');
if (!exist && (groupId === '10806' || groupId === '42' || groupId === '10807')) {
await this.semaphore?.addID(
idCommitmentHex,
'semaphore_taz_members',
merkleTreeRoot
);
}
}
await this.app!.updateLastGroup42BlockScanned(toBlock);
if (block.number > toBlock) {
await this.sync();
}
await this.app!.updateLastGroup42BlockScanned(toBlock);
if (block.number > toBlock) {
await this.sync();
}
}
}

View File

@@ -2,23 +2,23 @@ import { GenericService } from '../../util/svc';
import Group42 from './group42';
export class ReputationService extends GenericService {
group42?: Group42;
group42?: Group42;
constructor() {
super();
}
constructor() {
super();
}
async start() {
const app = await this.call('db', 'getApp');
this.group42 = new Group42({
app,
});
await this.group42.start();
this.sync();
}
async start() {
const app = await this.call('db', 'getApp');
this.group42 = new Group42({
app,
});
await this.group42.start();
this.sync();
}
sync = async () => {
await this.group42!.sync();
setTimeout(this.sync, 30000);
};
sync = async () => {
await this.group42!.sync();
setTimeout(this.sync, 30000);
};
}

View File

@@ -6,87 +6,87 @@ import config from '../../lib/zk-chat-server/src/utils/config';
import { RLN, RLNFullProof, SemaphoreFullProof } from '@zk-kit/protocols';
export default class ZKChatService extends GenericService {
zkchat: ZKChat;
sequelize: Sequelize;
zkchat: ZKChat;
sequelize: Sequelize;
constructor() {
super();
this.zkchat = new ZKChat();
this.sequelize = new Sequelize(
config.DB_NAME as string,
config.DB_USERNAME as string,
config.DB_PASSWORD,
{
host: config.DB_HOST,
port: Number(config.DB_PORT),
dialect: config.DB_DIALECT as Dialect,
logging: false,
}
);
}
constructor() {
super();
this.zkchat = new ZKChat();
this.sequelize = new Sequelize(
config.DB_NAME as string,
config.DB_USERNAME as string,
config.DB_PASSWORD,
{
host: config.DB_HOST,
port: Number(config.DB_PORT),
dialect: config.DB_DIALECT as Dialect,
logging: false,
}
);
}
start = async () => {
return this.zkchat.init();
};
start = async () => {
return this.zkchat.init();
};
registerUser = async (address: string, ecdhPubkey: string) => {
return this.zkchat.registerUser(address, ecdhPubkey);
};
registerUser = async (address: string, ecdhPubkey: string) => {
return this.zkchat.registerUser(address, ecdhPubkey);
};
getAllUsers = async (offset = 0, limit = 20) => {
return this.zkchat.getAllUsers(offset, limit);
};
getAllUsers = async (offset = 0, limit = 20) => {
return this.zkchat.getAllUsers(offset, limit);
};
addChatMessage = async (chatMessage: ChatMessage) => {
return this.zkchat.addChatMessage(chatMessage);
};
addChatMessage = async (chatMessage: ChatMessage) => {
return this.zkchat.addChatMessage(chatMessage);
};
getDirectMessages = async (
senderPubkey: string,
receiverPubkey: string,
offset = 0,
limit = 20
) => {
return this.zkchat.getDirectMessages(senderPubkey, receiverPubkey, offset, limit);
};
getDirectMessages = async (
senderPubkey: string,
receiverPubkey: string,
offset = 0,
limit = 20
) => {
return this.zkchat.getDirectMessages(senderPubkey, receiverPubkey, offset, limit);
};
getDirectChatsForUser = async (pubkey: string) => {
return this.zkchat.getDirectChatsForUser(pubkey);
};
getDirectChatsForUser = async (pubkey: string) => {
return this.zkchat.getDirectChatsForUser(pubkey);
};
isEpochCurrent = async (epoch: string) => {
return this.zkchat.isEpochCurrent(epoch);
};
isEpochCurrent = async (epoch: string) => {
return this.zkchat.isEpochCurrent(epoch);
};
verifyRLNProof = async (proof: RLNFullProof) => {
return this.zkchat.verifyRLNProof(proof);
};
verifyRLNProof = async (proof: RLNFullProof) => {
return this.zkchat.verifyRLNProof(proof);
};
verifySemaphoreProof = async (proof: SemaphoreFullProof) => {
return this.zkchat.verifySemaphoreProof(proof);
};
verifySemaphoreProof = async (proof: SemaphoreFullProof) => {
return this.zkchat.verifySemaphoreProof(proof);
};
checkShare = async (share: {
nullifier: string;
epoch: string;
x_share: string;
y_share: string;
}) => {
return this.zkchat.checkShare(share);
};
checkShare = async (share: {
nullifier: string;
epoch: string;
x_share: string;
y_share: string;
}) => {
return this.zkchat.checkShare(share);
};
insertShare = async (share: {
nullifier: string;
epoch: string;
x_share: string;
y_share: string;
}) => {
return this.zkchat.insertShare(share);
};
insertShare = async (share: {
nullifier: string;
epoch: string;
x_share: string;
y_share: string;
}) => {
return this.zkchat.insertShare(share);
};
searchChats = async (query: string, sender?: string, offset = 0, limit = 20) => {
const values = await this.sequelize.query(
`
searchChats = async (query: string, sender?: string, offset = 0, limit = 20) => {
const values = await this.sequelize.query(
`
SELECT
ecdh.value as receiver_ecdh,
idcommitment.value as receiver_idcommitment,
@@ -103,9 +103,9 @@ export default class ZKChatService extends GenericService {
OR LOWER(name.creator) IN (SELECT LOWER(creator) from profiles WHERE subtype = 'NAME' AND LOWER(value) LIKE :query ORDER BY "createdAt" DESC LIMIT 1)
)
${
!sender
? ''
: `
!sender
? ''
: `
AND (
zku.wallet_address IN (SELECT distinct zk.receiver_address FROM zkchat_chats zk WHERE zk.sender_address = :sender)
OR zku.wallet_address IN (SELECT distinct zk.sender_address FROM zkchat_chats zk WHERE zk.receiver_address = :sender)
@@ -114,17 +114,17 @@ export default class ZKChatService extends GenericService {
LIMIT :limit OFFSET :offset
`,
{
type: QueryTypes.SELECT,
replacements: {
query: `%${query.toLowerCase()}%`,
sender,
limit,
offset,
},
}
);
{
type: QueryTypes.SELECT,
replacements: {
query: `%${query.toLowerCase()}%`,
sender,
limit,
offset,
},
}
);
return values;
};
return values;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,46 +2,46 @@ import fs from 'fs';
import path from 'path';
let json: {
interrepAPI?: string;
web3HttpProvider?: string;
arbitrumHttpProvider?: string;
goerliHttpProvider?: string;
arbitrumRegistrar?: string;
arbitrumPrivateKey?: string;
arbitrumAddress?: string;
ensResolver?: string;
interrepContract?: string;
dbDialect?: string;
dbStorage?: string;
dbName?: string;
dbUsername?: string;
dbPassword?: string;
dbHost?: string;
dbPort?: string;
port?: number;
gunPort?: number;
gunPeers?: string[];
moderators?: string[];
jwtSecret?: string;
twCallbackUrl?: string;
twConsumerKey?: string;
twConsumerSecret?: string;
twBearerToken?: string;
twAccessKey?: string;
twAccessSecret?: string;
rapidAPIKey?: string;
web3StorageAPIKey?: string;
interrepAPI?: string;
web3HttpProvider?: string;
arbitrumHttpProvider?: string;
goerliHttpProvider?: string;
arbitrumRegistrar?: string;
arbitrumPrivateKey?: string;
arbitrumAddress?: string;
ensResolver?: string;
interrepContract?: string;
dbDialect?: string;
dbStorage?: string;
dbName?: string;
dbUsername?: string;
dbPassword?: string;
dbHost?: string;
dbPort?: string;
port?: number;
gunPort?: number;
gunPeers?: string[];
moderators?: string[];
jwtSecret?: string;
twCallbackUrl?: string;
twConsumerKey?: string;
twConsumerSecret?: string;
twBearerToken?: string;
twAccessKey?: string;
twAccessSecret?: string;
rapidAPIKey?: string;
web3StorageAPIKey?: string;
} = {};
try {
const configBuffer =
process.env.NODE_ENV === 'production'
? fs.readFileSync(path.join(process.cwd(), 'config.prod.json'))
: process.env.NODE_ENV === 'test'
? fs.readFileSync(path.join(process.cwd(), 'config.test.json'))
: fs.readFileSync(path.join(process.cwd(), 'config.dev.json'));
const parsed = JSON.parse(configBuffer.toString('utf-8'));
json = parsed;
const configBuffer =
process.env.NODE_ENV === 'production'
? fs.readFileSync(path.join(process.cwd(), 'config.prod.json'))
: process.env.NODE_ENV === 'test'
? fs.readFileSync(path.join(process.cwd(), 'config.test.json'))
: fs.readFileSync(path.join(process.cwd(), 'config.dev.json'));
const parsed = JSON.parse(configBuffer.toString('utf-8'));
json = parsed;
} catch (e) {}
const rapidAPIKey = json.rapidAPIKey || process.env.RAPIDAPI_KEY;
@@ -69,10 +69,10 @@ const port = json.port || process.env.PORT;
const gunPort = json.gunPort || process.env.GUN_PORT;
const gunPeers = json.gunPeers || process.env?.GUN_PEERS?.split(' ') || [];
const moderators = json.moderators ||
process.env?.MODERATORS?.split(' ') || [
'0x3F425586D68616A113C29c303766DAD444167EE8',
'0xd44a82dD160217d46D754a03C8f841edF06EBE3c',
];
process.env?.MODERATORS?.split(' ') || [
'0x3F425586D68616A113C29c303766DAD444167EE8',
'0xd44a82dD160217d46D754a03C8f841edF06EBE3c',
];
const interrepAPI = json.interrepAPI || process.env.INTERREP_API || 'https://kovan.interep.link';
const interrepContract = json.interrepContract || process.env.INTERREP_CONTRACT || '';
const jwtSecret = json.jwtSecret || process.env.JWT_SECRET || 'topjwtsecret';
@@ -96,35 +96,35 @@ if (!rapidAPIKey) throw new Error(`rapidAPIKey is not valid`);
if (!web3StorageAPIKey) throw new Error(`web3StorageAPIKey is not valid`);
const config = {
interrepAPI,
web3HttpProvider,
arbitrumHttpProvider,
goerliHttpProvider,
arbitrumRegistrar,
arbitrumPrivateKey,
arbitrumAddress,
ensResolver,
interrepContract,
dbDialect,
dbStorage,
dbName,
dbUsername,
dbPassword,
dbHost,
dbPort,
port: port ? Number(port) : 3000,
gunPort: gunPort ? Number(gunPort) : 8765,
gunPeers,
jwtSecret,
twCallbackUrl,
twConsumerKey,
twConsumerSecret,
twBearerToken,
twAccessKey,
twAccessSecret,
rapidAPIKey,
moderators,
web3StorageAPIKey,
interrepAPI,
web3HttpProvider,
arbitrumHttpProvider,
goerliHttpProvider,
arbitrumRegistrar,
arbitrumPrivateKey,
arbitrumAddress,
ensResolver,
interrepContract,
dbDialect,
dbStorage,
dbName,
dbUsername,
dbPassword,
dbHost,
dbPort,
port: port ? Number(port) : 3000,
gunPort: gunPort ? Number(gunPort) : 8765,
gunPeers,
jwtSecret,
twCallbackUrl,
twConsumerKey,
twConsumerSecret,
twBearerToken,
twAccessKey,
twAccessSecret,
rapidAPIKey,
moderators,
web3StorageAPIKey,
};
export default config;

View File

@@ -4,15 +4,15 @@ import tape from 'tape';
import { verifySignatureP256 } from './crypto';
tape('crypto.ts', async t => {
const signature =
'304402205eef94ed090d04f273b756a206dcabe875dc22e3be863d9d45065c2e405b3bdc022042f676412c5f0579c256f2ee7744950e3a661695e270c7bbaddbff9be2ac8357.0xf622d6eC8a21532a62BA2CAFdda571c24D670E5c';
const [sig, address] = signature.split('.');
const signature =
'304402205eef94ed090d04f273b756a206dcabe875dc22e3be863d9d45065c2e405b3bdc022042f676412c5f0579c256f2ee7744950e3a661695e270c7bbaddbff9be2ac8357.0xf622d6eC8a21532a62BA2CAFdda571c24D670E5c';
const [sig, address] = signature.split('.');
const verified = verifySignatureP256(
'cl-5YNh_qXWMxfn5rBiHgkJhNE8t4C6ESW6miFfo6Hw.sditz_GeLXkkfKblos8X6hZlH3HWCHUnvJTwhCItYp4',
'0xf622d6eC8a21532a62BA2CAFdda571c24D670E5c',
sig
);
t.ok(verified, 'should verified signature');
t.end();
const verified = verifySignatureP256(
'cl-5YNh_qXWMxfn5rBiHgkJhNE8t4C6ESW6miFfo6Hw.sditz_GeLXkkfKblos8X6hZlH3HWCHUnvJTwhCItYp4',
'0xf622d6eC8a21532a62BA2CAFdda571c24D670E5c',
sig
);
t.ok(verified, 'should verified signature');
t.end();
});

View File

@@ -1,27 +1,27 @@
import EC from 'elliptic';
export function base64ToArrayBuffer(base64: string): ArrayBuffer {
const binary_string = Buffer.from(
base64.replace(/_/g, '/').replace(/-/g, '+'),
'base64'
).toString('binary');
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
const binary_string = Buffer.from(
base64.replace(/_/g, '/').replace(/-/g, '+'),
'base64'
).toString('binary');
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
export function verifySignatureP256(pubkey: string, data: string, signature: string): boolean {
const [x, y] = pubkey.split('.');
const ec = new EC.ec('p256');
const pub = ec.keyFromPublic({
x: Buffer.from(base64ToArrayBuffer(x)).toString('hex'),
y: Buffer.from(base64ToArrayBuffer(y)).toString('hex'),
});
return pub.verify(
Buffer.from(data, 'utf-8').toString('hex'),
Buffer.from(signature, 'hex').toJSON().data
);
const [x, y] = pubkey.split('.');
const ec = new EC.ec('p256');
const pub = ec.keyFromPublic({
x: Buffer.from(base64ToArrayBuffer(x)).toString('hex'),
y: Buffer.from(base64ToArrayBuffer(y)).toString('hex'),
});
return pub.verify(
Buffer.from(data, 'utf-8').toString('hex'),
Buffer.from(signature, 'hex').toJSON().data
);
}

View File

@@ -3,7 +3,7 @@ import { interrepABI } from './abi';
import config from './config';
const httpProvider = new Web3.providers.HttpProvider(
'https://kovan.infura.io/v3/4ccf3cd743eb42b5a8cb1c8b0c0160ee'
'https://kovan.infura.io/v3/4ccf3cd743eb42b5a8cb1c8b0c0160ee'
);
const web3 = new Web3(httpProvider);
const interrep = new web3.eth.Contract(interrepABI as any, config.interrepContract);

View File

@@ -3,26 +3,26 @@ const format = winston.format;
const { combine, timestamp, prettyPrint } = format;
const logger = winston.createLogger({
level: 'info',
format: combine(timestamp(), format.colorize(), format.json()),
transports: [
new winston.transports.File({
filename: 'error.log',
level: 'error',
}),
new winston.transports.File({
filename: 'combined.log',
}),
],
level: 'info',
format: combine(timestamp(), format.colorize(), format.json()),
transports: [
new winston.transports.File({
filename: 'error.log',
level: 'error',
}),
new winston.transports.File({
filename: 'combined.log',
}),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(
new winston.transports.Console({
level: 'error',
format: winston.format.simple(),
})
);
logger.add(
new winston.transports.Console({
level: 'error',
format: winston.format.simple(),
})
);
}
export default logger;

View File

@@ -1,77 +1,77 @@
// @ts-ignore
import tape from 'tape';
import {
Connection,
ConnectionMessageSubType,
MessageType,
Moderation,
ModerationMessageSubType,
Post,
PostMessageSubType,
Profile,
ProfileMessageSubType,
Connection,
ConnectionMessageSubType,
MessageType,
Moderation,
ModerationMessageSubType,
Post,
PostMessageSubType,
Profile,
ProfileMessageSubType,
} from './message';
tape('message.ts', async t => {
const post1 = new Post({
type: MessageType.Post,
subtype: PostMessageSubType.Default,
creator: '0xmyuser',
payload: {
content: 'hello world',
},
});
const post1 = new Post({
type: MessageType.Post,
subtype: PostMessageSubType.Default,
creator: '0xmyuser',
payload: {
content: 'hello world',
},
});
const mod1 = new Moderation({
type: MessageType.Moderation,
subtype: ModerationMessageSubType.Like,
creator: '0xmyuser',
payload: {
reference: '0xmyuser/' + post1.hash(),
},
});
const mod1 = new Moderation({
type: MessageType.Moderation,
subtype: ModerationMessageSubType.Like,
creator: '0xmyuser',
payload: {
reference: '0xmyuser/' + post1.hash(),
},
});
const conn1 = new Connection({
type: MessageType.Connection,
subtype: ConnectionMessageSubType.Follow,
creator: '0xmyuser',
payload: {
name: '0xotheruser',
},
});
const conn1 = new Connection({
type: MessageType.Connection,
subtype: ConnectionMessageSubType.Follow,
creator: '0xmyuser',
payload: {
name: '0xotheruser',
},
});
const pfp1 = new Profile({
type: MessageType.Profile,
subtype: ProfileMessageSubType.Name,
creator: '0xmyuser',
payload: {
value: 'tsuk',
},
});
const pfp1 = new Profile({
type: MessageType.Profile,
subtype: ProfileMessageSubType.Name,
creator: '0xmyuser',
payload: {
value: 'tsuk',
},
});
t.deepEqual(
Post.fromHex(post1.toHex()).toJSON(),
post1.toJSON(),
'should serialize and deserialize Post'
);
t.deepEqual(
Post.fromHex(post1.toHex()).toJSON(),
post1.toJSON(),
'should serialize and deserialize Post'
);
t.deepEqual(
Moderation.fromHex(mod1.toHex()).toJSON(),
mod1.toJSON(),
'should serialize and deserialize Moderation'
);
t.deepEqual(
Moderation.fromHex(mod1.toHex()).toJSON(),
mod1.toJSON(),
'should serialize and deserialize Moderation'
);
t.deepEqual(
Connection.fromHex(conn1.toHex()).toJSON(),
conn1.toJSON(),
'should serialize and deserialize Connection'
);
t.deepEqual(
Connection.fromHex(conn1.toHex()).toJSON(),
conn1.toJSON(),
'should serialize and deserialize Connection'
);
t.deepEqual(
Profile.fromHex(pfp1.toHex()).toJSON(),
pfp1.toJSON(),
'should serialize and deserialize Profile'
);
t.deepEqual(
Profile.fromHex(pfp1.toHex()).toJSON(),
pfp1.toJSON(),
'should serialize and deserialize Profile'
);
t.end();
t.end();
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +1,32 @@
const mentionRegExp =
'[' +
'\\w-' +
// Latin-1 Supplement (letters only) - https://en.wikipedia.org/wiki/List_of_Unicode_characters#Latin-1_Supplement
'\u00C0-\u00D6' +
'\u00D8-\u00F6' +
'\u00F8-\u00FF' +
// Latin Extended-A (without deprecated character) - https://en.wikipedia.org/wiki/List_of_Unicode_characters#Latin_Extended-A
'\u0100-\u0148' +
'\u014A-\u017F' +
// Cyrillic symbols: \u0410-\u044F - https://en.wikipedia.org/wiki/Cyrillic_script_in_Unicode
'\u0410-\u044F' +
// hiragana (japanese): \u3040-\u309F - https://gist.github.com/ryanmcgrath/982242#file-japaneseregex-js
'\u3040-\u309F' +
// katakana (japanese): \u30A0-\u30FF - https://gist.github.com/ryanmcgrath/982242#file-japaneseregex-js
'\u30A0-\u30FF' +
// For an advanced explaination about Hangul see https://github.com/draft-js-plugins/draft-js-plugins/pull/480#issuecomment-254055437
// Hangul Jamo (korean): \u3130-\u318F - https://en.wikipedia.org/wiki/Korean_language_and_computers#Hangul_in_Unicode
// Hangul Syllables (korean): \uAC00-\uD7A3 - https://en.wikipedia.org/wiki/Korean_language_and_computers#Hangul_in_Unicode
'\u3130-\u318F' +
'\uAC00-\uD7A3' +
// common chinese symbols: \u4e00-\u9eff - http://stackoverflow.com/a/1366113/837709
// extended to \u9fa5 https://github.com/draft-js-plugins/draft-js-plugins/issues/1888
'\u4e00-\u9fa5' +
// Arabic https://en.wikipedia.org/wiki/Arabic_(Unicode_block)
'\u0600-\u06ff' +
// Vietnamese http://vietunicode.sourceforge.net/charset/
'\u00C0-\u1EF9' +
'\u002E' +
']';
'[' +
'\\w-' +
// Latin-1 Supplement (letters only) - https://en.wikipedia.org/wiki/List_of_Unicode_characters#Latin-1_Supplement
'\u00C0-\u00D6' +
'\u00D8-\u00F6' +
'\u00F8-\u00FF' +
// Latin Extended-A (without deprecated character) - https://en.wikipedia.org/wiki/List_of_Unicode_characters#Latin_Extended-A
'\u0100-\u0148' +
'\u014A-\u017F' +
// Cyrillic symbols: \u0410-\u044F - https://en.wikipedia.org/wiki/Cyrillic_script_in_Unicode
'\u0410-\u044F' +
// hiragana (japanese): \u3040-\u309F - https://gist.github.com/ryanmcgrath/982242#file-japaneseregex-js
'\u3040-\u309F' +
// katakana (japanese): \u30A0-\u30FF - https://gist.github.com/ryanmcgrath/982242#file-japaneseregex-js
'\u30A0-\u30FF' +
// For an advanced explaination about Hangul see https://github.com/draft-js-plugins/draft-js-plugins/pull/480#issuecomment-254055437
// Hangul Jamo (korean): \u3130-\u318F - https://en.wikipedia.org/wiki/Korean_language_and_computers#Hangul_in_Unicode
// Hangul Syllables (korean): \uAC00-\uD7A3 - https://en.wikipedia.org/wiki/Korean_language_and_computers#Hangul_in_Unicode
'\u3130-\u318F' +
'\uAC00-\uD7A3' +
// common chinese symbols: \u4e00-\u9eff - http://stackoverflow.com/a/1366113/837709
// extended to \u9fa5 https://github.com/draft-js-plugins/draft-js-plugins/issues/1888
'\u4e00-\u9fa5' +
// Arabic https://en.wikipedia.org/wiki/Arabic_(Unicode_block)
'\u0600-\u06ff' +
// Vietnamese http://vietunicode.sourceforge.net/charset/
'\u00C0-\u1EF9' +
'\u002E' +
']';
export const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
export const MENTION_REGEX = new RegExp(`\@${mentionRegExp}+`, 'g');

View File

@@ -4,29 +4,29 @@ import { Dialect, Sequelize } from 'sequelize';
let cached: Sequelize;
function getSequelize(): Sequelize {
if (cached) return sequelize;
if (cached) return sequelize;
if (!config.dbDialect || config.dbDialect === 'sqlite') {
cached = new Sequelize({
dialect: 'sqlite',
storage: config.dbStorage,
logging: false,
});
} else {
cached = new Sequelize(
config.dbName as string,
config.dbUsername as string,
config.dbPassword,
{
host: config.dbHost,
port: Number(config.dbPort),
dialect: config.dbDialect as Dialect,
logging: false,
}
);
}
if (!config.dbDialect || config.dbDialect === 'sqlite') {
cached = new Sequelize({
dialect: 'sqlite',
storage: config.dbStorage,
logging: false,
});
} else {
cached = new Sequelize(
config.dbName as string,
config.dbUsername as string,
config.dbPassword,
{
host: config.dbHost,
port: Number(config.dbPort),
dialect: config.dbDialect as Dialect,
logging: false,
}
);
}
return cached;
return cached;
}
export const sequelize = getSequelize();

View File

@@ -1,18 +1,18 @@
import { Response } from 'express';
type SSEClient = {
res: Response;
lastUsed: number;
res: Response;
lastUsed: number;
};
const CLIENT_CACHE: {
[clientId: string]: SSEClient;
[clientId: string]: SSEClient;
} = {};
const TOPIC_MAP: {
[topic: string]: {
[clientId: string]: boolean;
};
[topic: string]: {
[clientId: string]: boolean;
};
} = {};
const MAX_TTL = 2 * 60 * 1000;
@@ -20,109 +20,109 @@ const MAX_TTL = 2 * 60 * 1000;
let pruneTimeout: any;
export enum SSEType {
INIT = 'INIT',
NEW_CHAT_MESSAGE = 'NEW_CHAT_MESSAGE',
HEALTHCHECK = 'HEALTHCHECK',
INIT = 'INIT',
NEW_CHAT_MESSAGE = 'NEW_CHAT_MESSAGE',
HEALTHCHECK = 'HEALTHCHECK',
}
export const addConnection = (clientId: string, res: Response) => {
CLIENT_CACHE[clientId] = {
res,
lastUsed: Date.now(),
};
CLIENT_CACHE[clientId] = {
res,
lastUsed: Date.now(),
};
const raw = `data: ${JSON.stringify({
type: SSEType.INIT,
clientId,
})}\n\n`;
const raw = `data: ${JSON.stringify({
type: SSEType.INIT,
clientId,
})}\n\n`;
res.write(raw);
res.write(raw);
};
export const keepAlive = (clientId: string) => {
const client = CLIENT_CACHE[clientId];
const client = CLIENT_CACHE[clientId];
if (!client) throw new Error(`${clientId} not found`);
if (!client) throw new Error(`${clientId} not found`);
client.lastUsed = Date.now();
client.lastUsed = Date.now();
};
export const addTopic = (clientId: string, topic: string) => {
const client = CLIENT_CACHE[clientId];
const client = CLIENT_CACHE[clientId];
if (!client) {
throw new Error(`${clientId} not found`);
}
if (!client) {
throw new Error(`${clientId} not found`);
}
const bucket: { [id: string]: boolean } = TOPIC_MAP[topic] || {};
bucket[clientId] = true;
TOPIC_MAP[topic] = bucket;
const bucket: { [id: string]: boolean } = TOPIC_MAP[topic] || {};
bucket[clientId] = true;
TOPIC_MAP[topic] = bucket;
};
export const removeTopic = (clientId: string, topic: string) => {
const client = CLIENT_CACHE[clientId];
const client = CLIENT_CACHE[clientId];
if (!client) {
throw new Error(`${clientId} not found`);
}
if (!client) {
throw new Error(`${clientId} not found`);
}
const bucket: { [id: string]: boolean } = TOPIC_MAP[topic] || {};
const bucket: { [id: string]: boolean } = TOPIC_MAP[topic] || {};
if (bucket[clientId]) delete bucket[clientId];
if (bucket[clientId]) delete bucket[clientId];
TOPIC_MAP[topic] = bucket;
TOPIC_MAP[topic] = bucket;
};
export const publishTopic = async (topic: string, data: any) => {
const bucket: { [id: string]: boolean } = TOPIC_MAP[topic] || {};
const bucket: { [id: string]: boolean } = TOPIC_MAP[topic] || {};
for (const clientId in bucket) {
const client = CLIENT_CACHE[clientId];
if (!client) {
delete bucket[clientId];
} else {
const raw = `data: ${JSON.stringify(data)}\n\n`;
client.res.write(raw);
}
for (const clientId in bucket) {
const client = CLIENT_CACHE[clientId];
if (!client) {
delete bucket[clientId];
} else {
const raw = `data: ${JSON.stringify(data)}\n\n`;
client.res.write(raw);
}
}
};
export const removeConnection = (clientId: string) => {
const client = CLIENT_CACHE[clientId];
const client = CLIENT_CACHE[clientId];
if (!client) return;
if (!client) return;
delete CLIENT_CACHE[clientId];
delete CLIENT_CACHE[clientId];
client.res.end();
client.res.end();
};
export const getConnection = (clientId: string) => {
return CLIENT_CACHE[clientId];
return CLIENT_CACHE[clientId];
};
export const pruneConnections = async () => {
const now = Date.now();
for (const clientId in CLIENT_CACHE) {
const client = CLIENT_CACHE[clientId];
if (now - client.lastUsed > MAX_TTL) {
removeConnection(clientId);
for (const topic in TOPIC_MAP) {
const bucket = TOPIC_MAP[topic];
if (bucket[clientId]) delete bucket[clientId];
}
}
const now = Date.now();
for (const clientId in CLIENT_CACHE) {
const client = CLIENT_CACHE[clientId];
if (now - client.lastUsed > MAX_TTL) {
removeConnection(clientId);
for (const topic in TOPIC_MAP) {
const bucket = TOPIC_MAP[topic];
if (bucket[clientId]) delete bucket[clientId];
}
}
}
};
const pruneLoop = async () => {
if (pruneTimeout) {
clearTimeout(pruneTimeout);
}
if (pruneTimeout) {
clearTimeout(pruneTimeout);
}
await pruneConnections();
await pruneConnections();
setTimeout(pruneLoop, MAX_TTL);
setTimeout(pruneLoop, MAX_TTL);
};
pruneLoop();

View File

@@ -3,17 +3,17 @@ import tape from 'tape';
import { GenericService, MainService } from './svc';
class SampleService extends GenericService {
return = (value: any) => value;
return = (value: any) => value;
async start() {}
async start() {}
}
tape('svc.ts', async t => {
const main = new MainService();
main.add('sample', new SampleService());
await main.start();
const main = new MainService();
main.add('sample', new SampleService());
await main.start();
t.equal(await main.call('sample', 'return', 23), 23, 'should invoke service calls');
t.equal(await main.call('sample', 'return', 23), 23, 'should invoke service calls');
t.end();
t.end();
});

View File

@@ -3,99 +3,99 @@ import logger from './logger';
let callerId = 0;
export class GenericService {
name: string;
main?: MainService;
name: string;
main?: MainService;
constructor() {
this.name = '';
}
constructor() {
this.name = '';
}
async call(name: string, methodName: string, ...args: any[]) {
const id = callerId++;
// logger.debug(`called ${name}.${methodName}`, {
// ...args,
// origin: this.name,
// id: id,
// });
async call(name: string, methodName: string, ...args: any[]) {
const id = callerId++;
// logger.debug(`called ${name}.${methodName}`, {
// ...args,
// origin: this.name,
// id: id,
// });
if (this.main) {
const service: any = this.main.services[name];
const method = service[methodName];
if (typeof method === 'function') {
try {
const resp = await method.apply(service, args);
// logger.debug(`handled ${name}.${methodName}`, {
// origin: this.name,
// id: id,
// });
return resp;
} catch (e) {
logger.error(e.message, {
method: `${name}.${methodName}`,
origin: this.name,
id: id,
});
return Promise.reject(e);
}
} else {
logger.error(`${name}.${methodName} is not a function`, {
origin: this.name,
id: id,
});
return Promise.reject(new Error(`${name}.${methodName} is not a function`));
}
}
logger.error('main service not found', {
if (this.main) {
const service: any = this.main.services[name];
const method = service[methodName];
if (typeof method === 'function') {
try {
const resp = await method.apply(service, args);
// logger.debug(`handled ${name}.${methodName}`, {
// origin: this.name,
// id: id,
// });
return resp;
} catch (e) {
logger.error(e.message, {
method: `${name}.${methodName}`,
origin: this.name,
id: id,
});
return Promise.reject(e);
}
} else {
logger.error(`${name}.${methodName} is not a function`, {
origin: this.name,
id: id,
});
return Promise.reject(new Error('Main service not found'));
return Promise.reject(new Error(`${name}.${methodName} is not a function`));
}
}
async start() {}
logger.error('main service not found', {
origin: this.name,
id: id,
});
async stop() {}
return Promise.reject(new Error('Main service not found'));
}
async start() {}
async stop() {}
}
export class MainService extends GenericService {
services: {
[name: string]: GenericService;
};
services: {
[name: string]: GenericService;
};
constructor() {
super();
this.services = {};
this.main = this;
}
constructor() {
super();
this.services = {};
this.main = this;
}
add(name: string, service: GenericService): MainService {
service.name = name;
this.services[name] = service;
service.main = this;
logger.info(`added ${name}`, {
service: name,
add(name: string, service: GenericService): MainService {
service.name = name;
this.services[name] = service;
service.main = this;
logger.info(`added ${name}`, {
service: name,
});
return this;
}
async start() {
for (const name in this.services) {
logger.info(`starting ${name}`, {
service: name,
});
try {
await this.services[name].start();
logger.info(`started ${name}`, {
service: name,
});
return this;
}
async start() {
for (const name in this.services) {
logger.info(`starting ${name}`, {
service: name,
});
try {
await this.services[name].start();
logger.info(`started ${name}`, {
service: name,
});
} catch (e) {
logger.error(e.message, {
service: name,
});
return Promise.reject(e);
}
}
} catch (e) {
logger.error(e.message, {
service: name,
});
return Promise.reject(e);
}
}
}
}

View File

@@ -12,54 +12,54 @@ if (fs.existsSync(gunpath)) fs.unlinkSync(gunpath);
if (fs.existsSync(dbpath)) fs.unlinkSync(dbpath);
export const getMockDB = async () => {
if (!db) {
db = new DBService();
await db.start();
if (!db) {
db = new DBService();
await db.start();
await parse('connections.csv', db.connections!.createConnection);
await parse('ens.csv', ({ ens, address }) => db!.ens!.update(ens, address));
await parse('interep_groups.csv', ({ root_hash, provider, name }) =>
db!.interepGroups!.addHash(root_hash, provider, name)
);
await parse('links.csv', db.linkPreview!.update);
await parse('meta.csv', db.meta!.update);
await parse('moderations.csv', db.moderations!.createModeration);
await parse('posts.csv', db.posts!.createPost);
await parse('profiles.csv', db.profiles!.createProfile);
await parse('semaphore_creators.csv', ({ group, provider, message_id }) =>
db!.semaphoreCreators!.addSemaphoreCreator(message_id, provider, group)
);
await parse('semaphores.csv', ({ id_commitment, provider, name }) =>
db!.semaphore!.addID(id_commitment, provider, name)
);
await parse('tags.csv', ({ tag_name, message_id }) =>
db!.tags!.addTagPost(tag_name, message_id)
);
await parse('threads.csv', ({ root_id, message_id }) =>
db!.threads!.addThreadData(root_id, message_id)
);
await parse('usermeta.csv', db.userMeta!.update);
await parse('users.csv', db.users!.updateOrCreateUser);
}
await parse('connections.csv', db.connections!.createConnection);
await parse('ens.csv', ({ ens, address }) => db!.ens!.update(ens, address));
await parse('interep_groups.csv', ({ root_hash, provider, name }) =>
db!.interepGroups!.addHash(root_hash, provider, name)
);
await parse('links.csv', db.linkPreview!.update);
await parse('meta.csv', db.meta!.update);
await parse('moderations.csv', db.moderations!.createModeration);
await parse('posts.csv', db.posts!.createPost);
await parse('profiles.csv', db.profiles!.createProfile);
await parse('semaphore_creators.csv', ({ group, provider, message_id }) =>
db!.semaphoreCreators!.addSemaphoreCreator(message_id, provider, group)
);
await parse('semaphores.csv', ({ id_commitment, provider, name }) =>
db!.semaphore!.addID(id_commitment, provider, name)
);
await parse('tags.csv', ({ tag_name, message_id }) =>
db!.tags!.addTagPost(tag_name, message_id)
);
await parse('threads.csv', ({ root_id, message_id }) =>
db!.threads!.addThreadData(root_id, message_id)
);
await parse('usermeta.csv', db.userMeta!.update);
await parse('users.csv', db.users!.updateOrCreateUser);
}
return db;
return db;
};
const parse = async (filename: string, insertFn: (data: any) => any) => {
return new Promise((resolve, reject) => {
const buf = fs.readFileSync(path.join(process.cwd(), 'static', 'tests', filename));
csv.parse(
buf,
{
delimiter: ',',
skip_empty_lines: true,
columns: true,
},
async (err, rows) => {
if (err) return reject(err);
await Promise.all(rows.map((row: any) => insertFn(row)));
resolve(rows);
}
);
});
return new Promise((resolve, reject) => {
const buf = fs.readFileSync(path.join(process.cwd(), 'static', 'tests', filename));
csv.parse(
buf,
{
delimiter: ',',
skip_empty_lines: true,
columns: true,
},
async (err, rows) => {
if (err) return reject(err);
await Promise.all(rows.map((row: any) => insertFn(row)));
resolve(rows);
}
);
});
};

View File

@@ -3,307 +3,307 @@ import sinon, { SinonStub } from 'sinon';
let fetchStub: any;
export const stubFetch = () => {
// @ts-ignore
fetchStub = fetchStub || sinon.stub(global, 'fetch');
return fetchStub;
// @ts-ignore
fetchStub = fetchStub || sinon.stub(global, 'fetch');
return fetchStub;
};
export const stubCall = (
service: any
service: any
): [
SinonStub,
{
app: {
read: SinonStub;
updateLastArbitrumBlock: SinonStub;
};
ens: {
update: SinonStub;
};
sempahore: {
addID: SinonStub;
removeID: SinonStub;
findOneByCommitment: SinonStub;
findAllByCommitment: SinonStub;
};
interepGroups: {
findOneByHash: SinonStub;
addHash: SinonStub;
};
users: {
updateOrCreateUser: SinonStub;
findOneByName: SinonStub;
readAll: SinonStub;
search: SinonStub;
ensureUser: SinonStub;
findOneByPubkey: SinonStub;
};
posts: {
findLastTweetInConversation: SinonStub;
findAllRepliesFromCreator: SinonStub;
findAllLikedPostsByCreator: SinonStub;
getHomeFeed: SinonStub;
createTwitterPosts: SinonStub;
findAllPosts: SinonStub;
findAllReplies: SinonStub;
findRoot: SinonStub;
ensurePost: SinonStub;
findOne: SinonStub;
createPost: SinonStub;
remove: SinonStub;
};
meta: {
findTags: SinonStub;
addPost: SinonStub;
addReply: SinonStub;
addRepost: SinonStub;
addLike: SinonStub;
removePost: SinonStub;
removeReply: SinonStub;
removeRepost: SinonStub;
removeLike: SinonStub;
};
userMeta: {
addPostingCount: SinonStub;
addMentionedCount: SinonStub;
addFollower: SinonStub;
addFollowing: SinonStub;
addBlocked: SinonStub;
addBlocking: SinonStub;
removePostingCount: SinonStub;
removeMentionedCount: SinonStub;
removeFollower: SinonStub;
removeFollowing: SinonStub;
removeBlocked: SinonStub;
removeBlocking: SinonStub;
};
moderations: {
findOne: SinonStub;
createModeration: SinonStub;
remove: SinonStub;
};
connections: {
findOne: SinonStub;
createConnection: SinonStub;
remove: SinonStub;
};
profiles: {
findOne: SinonStub;
createProfile: SinonStub;
remove: SinonStub;
};
semaphoreCreators: {
addSemaphoreCreator: SinonStub;
};
tags: {
getPostsByTag: SinonStub;
addTagPost: SinonStub;
removeTagPost: SinonStub;
};
threads: {
addThreadData: SinonStub;
};
twitterAuths: {
findUserByToken: SinonStub;
addAccount: SinonStub;
};
link: {
read: SinonStub;
update: SinonStub;
};
merkle: {
getGroupByRoot: SinonStub;
};
zkchat: {
verifyRLNProof: SinonStub;
checkShare: SinonStub;
};
}
SinonStub,
{
app: {
read: SinonStub;
updateLastArbitrumBlock: SinonStub;
};
ens: {
update: SinonStub;
};
sempahore: {
addID: SinonStub;
removeID: SinonStub;
findOneByCommitment: SinonStub;
findAllByCommitment: SinonStub;
};
interepGroups: {
findOneByHash: SinonStub;
addHash: SinonStub;
};
users: {
updateOrCreateUser: SinonStub;
findOneByName: SinonStub;
readAll: SinonStub;
search: SinonStub;
ensureUser: SinonStub;
findOneByPubkey: SinonStub;
};
posts: {
findLastTweetInConversation: SinonStub;
findAllRepliesFromCreator: SinonStub;
findAllLikedPostsByCreator: SinonStub;
getHomeFeed: SinonStub;
createTwitterPosts: SinonStub;
findAllPosts: SinonStub;
findAllReplies: SinonStub;
findRoot: SinonStub;
ensurePost: SinonStub;
findOne: SinonStub;
createPost: SinonStub;
remove: SinonStub;
};
meta: {
findTags: SinonStub;
addPost: SinonStub;
addReply: SinonStub;
addRepost: SinonStub;
addLike: SinonStub;
removePost: SinonStub;
removeReply: SinonStub;
removeRepost: SinonStub;
removeLike: SinonStub;
};
userMeta: {
addPostingCount: SinonStub;
addMentionedCount: SinonStub;
addFollower: SinonStub;
addFollowing: SinonStub;
addBlocked: SinonStub;
addBlocking: SinonStub;
removePostingCount: SinonStub;
removeMentionedCount: SinonStub;
removeFollower: SinonStub;
removeFollowing: SinonStub;
removeBlocked: SinonStub;
removeBlocking: SinonStub;
};
moderations: {
findOne: SinonStub;
createModeration: SinonStub;
remove: SinonStub;
};
connections: {
findOne: SinonStub;
createConnection: SinonStub;
remove: SinonStub;
};
profiles: {
findOne: SinonStub;
createProfile: SinonStub;
remove: SinonStub;
};
semaphoreCreators: {
addSemaphoreCreator: SinonStub;
};
tags: {
getPostsByTag: SinonStub;
addTagPost: SinonStub;
removeTagPost: SinonStub;
};
threads: {
addThreadData: SinonStub;
};
twitterAuths: {
findUserByToken: SinonStub;
addAccount: SinonStub;
};
link: {
read: SinonStub;
update: SinonStub;
};
merkle: {
getGroupByRoot: SinonStub;
};
zkchat: {
verifyRLNProof: SinonStub;
checkShare: SinonStub;
};
}
] => {
const callStub = sinon.stub(service, 'call');
const callStub = sinon.stub(service, 'call');
const app = {
updateLastArbitrumBlock: sinon.stub(),
read: sinon.stub(),
};
const app = {
updateLastArbitrumBlock: sinon.stub(),
read: sinon.stub(),
};
const ens = {
update: sinon.stub(),
};
const ens = {
update: sinon.stub(),
};
const sempahore = {
addID: sinon.stub(),
removeID: sinon.stub(),
findOneByCommitment: sinon.stub(),
findAllByCommitment: sinon.stub(),
};
const sempahore = {
addID: sinon.stub(),
removeID: sinon.stub(),
findOneByCommitment: sinon.stub(),
findAllByCommitment: sinon.stub(),
};
const interepGroups = {
findOneByHash: sinon.stub(),
addHash: sinon.stub(),
};
const interepGroups = {
findOneByHash: sinon.stub(),
addHash: sinon.stub(),
};
const users = {
findOneByName: sinon.stub(),
readAll: sinon.stub(),
search: sinon.stub(),
ensureUser: sinon.stub(),
findOneByPubkey: sinon.stub(),
updateOrCreateUser: sinon.stub(),
};
const users = {
findOneByName: sinon.stub(),
readAll: sinon.stub(),
search: sinon.stub(),
ensureUser: sinon.stub(),
findOneByPubkey: sinon.stub(),
updateOrCreateUser: sinon.stub(),
};
const posts = {
findLastTweetInConversation: sinon.stub(),
findAllRepliesFromCreator: sinon.stub(),
findAllLikedPostsByCreator: sinon.stub(),
getHomeFeed: sinon.stub(),
findAllPosts: sinon.stub(),
findAllReplies: sinon.stub(),
createTwitterPosts: sinon.stub(),
findRoot: sinon.stub(),
ensurePost: sinon.stub(),
findOne: sinon.stub(),
createPost: sinon.stub(),
remove: sinon.stub(),
};
const posts = {
findLastTweetInConversation: sinon.stub(),
findAllRepliesFromCreator: sinon.stub(),
findAllLikedPostsByCreator: sinon.stub(),
getHomeFeed: sinon.stub(),
findAllPosts: sinon.stub(),
findAllReplies: sinon.stub(),
createTwitterPosts: sinon.stub(),
findRoot: sinon.stub(),
ensurePost: sinon.stub(),
findOne: sinon.stub(),
createPost: sinon.stub(),
remove: sinon.stub(),
};
const moderations = {
findOne: sinon.stub(),
createModeration: sinon.stub(),
remove: sinon.stub(),
};
const moderations = {
findOne: sinon.stub(),
createModeration: sinon.stub(),
remove: sinon.stub(),
};
const connections = {
findOne: sinon.stub(),
createConnection: sinon.stub(),
remove: sinon.stub(),
};
const connections = {
findOne: sinon.stub(),
createConnection: sinon.stub(),
remove: sinon.stub(),
};
const profiles = {
findOne: sinon.stub(),
createProfile: sinon.stub(),
remove: sinon.stub(),
};
const profiles = {
findOne: sinon.stub(),
createProfile: sinon.stub(),
remove: sinon.stub(),
};
const meta = {
findTags: sinon.stub(),
addPost: sinon.stub(),
addReply: sinon.stub(),
addRepost: sinon.stub(),
addLike: sinon.stub(),
removePost: sinon.stub(),
removeReply: sinon.stub(),
removeRepost: sinon.stub(),
removeLike: sinon.stub(),
};
const meta = {
findTags: sinon.stub(),
addPost: sinon.stub(),
addReply: sinon.stub(),
addRepost: sinon.stub(),
addLike: sinon.stub(),
removePost: sinon.stub(),
removeReply: sinon.stub(),
removeRepost: sinon.stub(),
removeLike: sinon.stub(),
};
const userMeta = {
addPostingCount: sinon.stub(),
addMentionedCount: sinon.stub(),
addFollower: sinon.stub(),
addFollowing: sinon.stub(),
addBlocked: sinon.stub(),
addBlocking: sinon.stub(),
removePostingCount: sinon.stub(),
removeMentionedCount: sinon.stub(),
removeFollower: sinon.stub(),
removeFollowing: sinon.stub(),
removeBlocked: sinon.stub(),
removeBlocking: sinon.stub(),
};
const userMeta = {
addPostingCount: sinon.stub(),
addMentionedCount: sinon.stub(),
addFollower: sinon.stub(),
addFollowing: sinon.stub(),
addBlocked: sinon.stub(),
addBlocking: sinon.stub(),
removePostingCount: sinon.stub(),
removeMentionedCount: sinon.stub(),
removeFollower: sinon.stub(),
removeFollowing: sinon.stub(),
removeBlocked: sinon.stub(),
removeBlocking: sinon.stub(),
};
const semaphoreCreators = {
addSemaphoreCreator: sinon.stub(),
};
const semaphoreCreators = {
addSemaphoreCreator: sinon.stub(),
};
const tags = {
getPostsByTag: sinon.stub(),
addTagPost: sinon.stub(),
removeTagPost: sinon.stub(),
};
const tags = {
getPostsByTag: sinon.stub(),
addTagPost: sinon.stub(),
removeTagPost: sinon.stub(),
};
const threads = {
addThreadData: sinon.stub(),
};
const threads = {
addThreadData: sinon.stub(),
};
const twitterAuths = {
addAccount: sinon.stub(),
findUserByToken: sinon.stub(),
};
const twitterAuths = {
addAccount: sinon.stub(),
findUserByToken: sinon.stub(),
};
const link = {
read: sinon.stub(),
update: sinon.stub(),
};
const link = {
read: sinon.stub(),
update: sinon.stub(),
};
const merkle = {
getGroupByRoot: sinon.stub(),
};
const merkle = {
getGroupByRoot: sinon.stub(),
};
const zkchat = {
verifyRLNProof: sinon.stub(),
checkShare: sinon.stub(),
};
const zkchat = {
verifyRLNProof: sinon.stub(),
checkShare: sinon.stub(),
};
callStub
.withArgs('db', 'getSemaphore')
.returns(Promise.resolve(sempahore))
.withArgs('db', 'getInterepGroups')
.returns(Promise.resolve(interepGroups))
.withArgs('db', 'getUsers')
.returns(Promise.resolve(users))
.withArgs('db', 'getPosts')
.returns(Promise.resolve(posts))
.withArgs('db', 'getMeta')
.returns(Promise.resolve(meta))
.withArgs('db', 'getUserMeta')
.returns(Promise.resolve(userMeta))
.withArgs('db', 'getSemaphoreCreators')
.returns(Promise.resolve(semaphoreCreators))
.withArgs('db', 'getTags')
.returns(Promise.resolve(tags))
.withArgs('db', 'getThreads')
.returns(Promise.resolve(threads))
.withArgs('db', 'getModerations')
.returns(Promise.resolve(moderations))
.withArgs('db', 'getConnections')
.returns(Promise.resolve(connections))
.withArgs('db', 'getProfiles')
.returns(Promise.resolve(profiles))
.withArgs('db', 'getTwitterAuth')
.returns(Promise.resolve(twitterAuths))
.withArgs('db', 'getENS')
.returns(Promise.resolve(ens))
.withArgs('db', 'getApp')
.returns(Promise.resolve(app))
.withArgs('db', 'getLinkPreview')
.returns(Promise.resolve(link))
.withArgs('db', 'getMerkle')
.returns(Promise.resolve(merkle))
.withArgs('db', 'getZKChat')
.returns(Promise.resolve(zkchat));
callStub
.withArgs('db', 'getSemaphore')
.returns(Promise.resolve(sempahore))
.withArgs('db', 'getInterepGroups')
.returns(Promise.resolve(interepGroups))
.withArgs('db', 'getUsers')
.returns(Promise.resolve(users))
.withArgs('db', 'getPosts')
.returns(Promise.resolve(posts))
.withArgs('db', 'getMeta')
.returns(Promise.resolve(meta))
.withArgs('db', 'getUserMeta')
.returns(Promise.resolve(userMeta))
.withArgs('db', 'getSemaphoreCreators')
.returns(Promise.resolve(semaphoreCreators))
.withArgs('db', 'getTags')
.returns(Promise.resolve(tags))
.withArgs('db', 'getThreads')
.returns(Promise.resolve(threads))
.withArgs('db', 'getModerations')
.returns(Promise.resolve(moderations))
.withArgs('db', 'getConnections')
.returns(Promise.resolve(connections))
.withArgs('db', 'getProfiles')
.returns(Promise.resolve(profiles))
.withArgs('db', 'getTwitterAuth')
.returns(Promise.resolve(twitterAuths))
.withArgs('db', 'getENS')
.returns(Promise.resolve(ens))
.withArgs('db', 'getApp')
.returns(Promise.resolve(app))
.withArgs('db', 'getLinkPreview')
.returns(Promise.resolve(link))
.withArgs('db', 'getMerkle')
.returns(Promise.resolve(merkle))
.withArgs('db', 'getZKChat')
.returns(Promise.resolve(zkchat));
return [
callStub,
{
app,
ens,
sempahore,
interepGroups,
users,
posts,
moderations,
connections,
meta,
userMeta,
semaphoreCreators,
tags,
threads,
profiles,
twitterAuths,
link,
merkle,
zkchat,
},
];
return [
callStub,
{
app,
ens,
sempahore,
interepGroups,
users,
posts,
moderations,
connections,
meta,
userMeta,
semaphoreCreators,
tags,
threads,
profiles,
twitterAuths,
link,
merkle,
zkchat,
},
];
};

View File

@@ -7,67 +7,67 @@ import { stubFetch } from './testUtils';
const fetchStub = stubFetch();
tape('twitter - requestToken', async t => {
fetchStub.returns(
Promise.resolve({
status: 200,
text: async () => 'token',
})
);
fetchStub.returns(
Promise.resolve({
status: 200,
text: async () => 'token',
})
);
const ret = await requestToken();
const ret = await requestToken();
t.equal(
fetchStub.args[0][0],
'https://api.twitter.com/oauth/request_token',
'should request token from twitter'
);
t.equal(
fetchStub.args[0][0],
'https://api.twitter.com/oauth/request_token',
'should request token from twitter'
);
t.equal(ret, 'token', 'should return text');
t.equal(ret, 'token', 'should return text');
fetchStub.reset();
t.end();
fetchStub.reset();
t.end();
});
tape('twitter - accessToken', async t => {
fetchStub.returns(
Promise.resolve({
status: 200,
text: async () => 'token',
})
);
fetchStub.returns(
Promise.resolve({
status: 200,
text: async () => 'token',
})
);
const ret = await accessToken('1', '2', '3');
const ret = await accessToken('1', '2', '3');
t.equal(
fetchStub.args[0][0],
'https://api.twitter.com/oauth/access_token',
'should access token from twitter'
);
t.equal(
fetchStub.args[0][0],
'https://api.twitter.com/oauth/access_token',
'should access token from twitter'
);
t.equal(ret, 'token', 'should return text');
t.equal(ret, 'token', 'should return text');
fetchStub.reset();
t.end();
fetchStub.reset();
t.end();
});
tape('twitter - verifyCredential', async t => {
fetchStub.returns(
Promise.resolve({
status: 200,
json: async () => 'token',
})
);
fetchStub.returns(
Promise.resolve({
status: 200,
json: async () => 'token',
})
);
const ret = await verifyCredential('1', '2');
const ret = await verifyCredential('1', '2');
t.equal(
fetchStub.args[0][0],
'https://api.twitter.com/1.1/account/verify_credentials.json',
'should access token from twitter'
);
t.equal(
fetchStub.args[0][0],
'https://api.twitter.com/1.1/account/verify_credentials.json',
'should access token from twitter'
);
t.equal(ret, 'token', 'should return text');
t.equal(ret, 'token', 'should return text');
fetchStub.reset();
t.end();
fetchStub.reset();
t.end();
});

View File

@@ -16,259 +16,259 @@ const TW_ACCESS_KEY = config.twAccessKey;
const TW_ACCESS_SECRET = config.twAccessSecret;
const botometer = new Botometer({
consumerKey: TW_CONSUMER_KEY,
consumerSecret: TW_CONSUMER_SECRET,
accessToken: TW_ACCESS_KEY,
accessTokenSecret: TW_ACCESS_SECRET,
rapidApiKey: config.rapidAPIKey,
usePro: true,
consumerKey: TW_CONSUMER_KEY,
consumerSecret: TW_CONSUMER_SECRET,
accessToken: TW_ACCESS_KEY,
accessTokenSecret: TW_ACCESS_SECRET,
rapidApiKey: config.rapidAPIKey,
usePro: true,
});
const oauth = OAuth({
consumer: {
key: TW_CONSUMER_KEY,
secret: TW_CONSUMER_SECRET,
},
signature_method: 'HMAC-SHA1',
hash_function: (baseString: string, key: string) => {
return crypto.createHmac('sha1', key).update(baseString).digest('base64');
},
consumer: {
key: TW_CONSUMER_KEY,
secret: TW_CONSUMER_SECRET,
},
signature_method: 'HMAC-SHA1',
hash_function: (baseString: string, key: string) => {
return crypto.createHmac('sha1', key).update(baseString).digest('base64');
},
});
export const createHeader = (requestData: any, key: string, secret: string) => {
const headers = oauth.toHeader(
oauth.authorize(requestData, {
key: key,
secret: secret,
})
);
return headers;
const headers = oauth.toHeader(
oauth.authorize(requestData, {
key: key,
secret: secret,
})
);
return headers;
};
export const requestToken = async (): Promise<string> => {
const requestData = {
url: TW_REQ_TOKEN_URL,
method: 'POST',
data: {
oauth_callbank: TW_CALLBACK_URL,
},
};
const requestData = {
url: TW_REQ_TOKEN_URL,
method: 'POST',
data: {
oauth_callbank: TW_CALLBACK_URL,
},
};
// @ts-ignore
const resp = await fetch(requestData.url, {
method: requestData.method,
form: requestData.data,
headers: {
...oauth.toHeader(oauth.authorize(requestData)),
'Content-Type': 'application/x-www-form-urlencoded',
},
});
// @ts-ignore
const resp = await fetch(requestData.url, {
method: requestData.method,
form: requestData.data,
headers: {
...oauth.toHeader(oauth.authorize(requestData)),
'Content-Type': 'application/x-www-form-urlencoded',
},
});
if (resp.status !== 200) throw new Error(resp.statusText);
if (resp.status !== 200) throw new Error(resp.statusText);
return await resp.text();
return await resp.text();
};
export const accessToken = async (token: string, verifier: string, tokenSecret: string) => {
const requestData = {
url: TW_ACCESS_TOKEN_URL,
method: 'POST',
data: {
oauth_token: token,
oauth_verifier: verifier,
oauth_token_secret: tokenSecret,
},
};
const requestData = {
url: TW_ACCESS_TOKEN_URL,
method: 'POST',
data: {
oauth_token: token,
oauth_verifier: verifier,
oauth_token_secret: tokenSecret,
},
};
// @ts-ignore
const resp = await fetch(requestData.url, {
method: requestData.method,
form: requestData.data,
headers: {
...oauth.toHeader(oauth.authorize(requestData)),
'Content-Type': 'application/x-www-form-urlencoded',
},
});
// @ts-ignore
const resp = await fetch(requestData.url, {
method: requestData.method,
form: requestData.data,
headers: {
...oauth.toHeader(oauth.authorize(requestData)),
'Content-Type': 'application/x-www-form-urlencoded',
},
});
if (resp.status !== 200) throw new Error(resp.statusText);
if (resp.status !== 200) throw new Error(resp.statusText);
return await resp.text();
return await resp.text();
};
export const verifyCredential = async (key: string, secret: string) => {
const headers = oauth.toHeader(
oauth.authorize(
{
url: `https://api.twitter.com/1.1/account/verify_credentials.json`,
method: 'GET',
},
{
key: key,
secret: secret,
}
)
);
const headers = oauth.toHeader(
oauth.authorize(
{
url: `https://api.twitter.com/1.1/account/verify_credentials.json`,
method: 'GET',
},
{
key: key,
secret: secret,
}
)
);
// @ts-ignore
const resp = await fetch(`https://api.twitter.com/1.1/account/verify_credentials.json`, {
headers: headers,
});
// @ts-ignore
const resp = await fetch(`https://api.twitter.com/1.1/account/verify_credentials.json`, {
headers: headers,
});
if (resp.status !== 200) {
throw new Error(resp.statusText);
}
if (resp.status !== 200) {
throw new Error(resp.statusText);
}
const json = await resp.json();
const json = await resp.json();
return json;
return json;
};
export const updateStatus = async (
status: string,
in_reply_to_status_id: string,
key: string,
secret: string
status: string,
in_reply_to_status_id: string,
key: string,
secret: string
) => {
const requestData = {
url: `https://api.twitter.com/1.1/statuses/update.json`,
method: 'POST',
data: {
status,
in_reply_to_status_id,
},
};
const headers = oauth.toHeader(
oauth.authorize(requestData, {
key: key,
secret: secret,
})
);
const requestData = {
url: `https://api.twitter.com/1.1/statuses/update.json`,
method: 'POST',
data: {
status,
in_reply_to_status_id,
},
};
const headers = oauth.toHeader(
oauth.authorize(requestData, {
key: key,
secret: secret,
})
);
// @ts-ignore
const resp = await fetch(requestData.url, {
method: requestData.method,
body: new URLSearchParams(requestData.data).toString(),
headers: {
...headers,
'Content-Type': 'application/x-www-form-urlencoded',
},
});
const json = await resp.json();
// @ts-ignore
const resp = await fetch(requestData.url, {
method: requestData.method,
body: new URLSearchParams(requestData.data).toString(),
headers: {
...headers,
'Content-Type': 'application/x-www-form-urlencoded',
},
});
const json = await resp.json();
if (resp.status === 200) {
return json;
} else {
throw new Error(json.errors[0].message);
}
if (resp.status === 200) {
return json;
} else {
throw new Error(json.errors[0].message);
}
};
export async function showStatus(id: string, key?: string, secret?: string) {
const requestData = {
url: `https://api.twitter.com/1.1/statuses/show/${id}.json`,
method: 'GET',
};
const headers = oauth.toHeader(
oauth.authorize(requestData, {
key: key || TW_ACCESS_KEY,
secret: secret || TW_ACCESS_SECRET,
})
);
const requestData = {
url: `https://api.twitter.com/1.1/statuses/show/${id}.json`,
method: 'GET',
};
const headers = oauth.toHeader(
oauth.authorize(requestData, {
key: key || TW_ACCESS_KEY,
secret: secret || TW_ACCESS_SECRET,
})
);
// @ts-ignore
const resp = await fetch(requestData.url, {
method: requestData.method,
headers: {
...headers,
},
});
// @ts-ignore
const resp = await fetch(requestData.url, {
method: requestData.method,
headers: {
...headers,
},
});
if (resp.status !== 200) {
throw new Error(resp.statusText);
}
if (resp.status !== 200) {
throw new Error(resp.statusText);
}
const json = await resp.json();
const json = await resp.json();
return json;
return json;
}
export async function getReplies(tweetUrl: string, lastTweetHash?: string): Promise<PostModel[]> {
const [__, _, tweetId] = tweetUrl.replace('https://twitter.com/', '').split('/');
const [__, _, tweetId] = tweetUrl.replace('https://twitter.com/', '').split('/');
const sinceId = lastTweetHash ? `&since_id=${lastTweetHash}` : '';
const qs =
'&max_results=100&expansions=author_id,in_reply_to_user_id&tweet.fields=referenced_tweets,in_reply_to_user_id,author_id,created_at,conversation_id&user.fields=name,username';
const sinceId = lastTweetHash ? `&since_id=${lastTweetHash}` : '';
const qs =
'&max_results=100&expansions=author_id,in_reply_to_user_id&tweet.fields=referenced_tweets,in_reply_to_user_id,author_id,created_at,conversation_id&user.fields=name,username';
// @ts-ignore
const resp = await fetch(
`https://api.twitter.com/2/tweets/search/recent?query=conversation_id:${tweetId}${sinceId}${qs}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${TW_BEARER_TOKEN}`,
},
}
);
// @ts-ignore
const resp = await fetch(
`https://api.twitter.com/2/tweets/search/recent?query=conversation_id:${tweetId}${sinceId}${qs}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${TW_BEARER_TOKEN}`,
},
}
);
const json = await resp.json();
const json = await resp.json();
if (json.errors) return [];
if (json.errors) return [];
const data = json.data || [];
const users = (json.includes?.users || []).reduce((acc: any, user: any) => {
acc[user.id] = user.username;
return acc;
}, {});
const data = json.data || [];
const users = (json.includes?.users || []).reduce((acc: any, user: any) => {
acc[user.id] = user.username;
return acc;
}, {});
return data.map(
(tweet: {
author_id: string;
created_at: string;
coversation_id: string;
in_reply_to_user_id: string;
text: string;
id: string;
referenced_tweets: { type: string; id: string }[];
}): PostModel => {
const reply = tweet.referenced_tweets.filter(({ type }) => type === 'replied_to')[0];
return {
messageId: tweet.id,
hash: tweet.id,
creator: users[tweet.author_id] || tweet.author_id,
type: '@TWEET@',
subtype: '',
createdAt: new Date(tweet.created_at).getTime(),
topic: '',
title: '',
content: tweet.text,
reference: reply ? reply.id : tweetId,
attachment: '',
};
}
);
return data.map(
(tweet: {
author_id: string;
created_at: string;
coversation_id: string;
in_reply_to_user_id: string;
text: string;
id: string;
referenced_tweets: { type: string; id: string }[];
}): PostModel => {
const reply = tweet.referenced_tweets.filter(({ type }) => type === 'replied_to')[0];
return {
messageId: tweet.id,
hash: tweet.id,
creator: users[tweet.author_id] || tweet.author_id,
type: '@TWEET@',
subtype: '',
createdAt: new Date(tweet.created_at).getTime(),
topic: '',
title: '',
content: tweet.text,
reference: reply ? reply.id : tweetId,
attachment: '',
};
}
);
}
export async function getUser(username: string): Promise<{
id: string;
name: string;
username: string;
profile_image_url: string;
id: string;
name: string;
username: string;
profile_image_url: string;
} | null> {
const qs = '?user.fields=name,username,profile_image_url';
const qs = '?user.fields=name,username,profile_image_url';
// @ts-ignore
const resp = await fetch(`https://api.twitter.com/2/users/by/username/${username}${qs}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${TW_BEARER_TOKEN}`,
},
});
// @ts-ignore
const resp = await fetch(`https://api.twitter.com/2/users/by/username/${username}${qs}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${TW_BEARER_TOKEN}`,
},
});
const json = await resp.json();
if (json.errors) return null;
const json = await resp.json();
if (json.errors) return null;
return json.data;
return json.data;
}
export async function getBotometerScore(username: string): Promise<any> {
return botometer.getScore(username);
return botometer.getScore(username);
}