mirror of
https://github.com/AtHeartEngineering/bandada.git
synced 2026-01-09 02:28:30 -05:00
22
README.md
22
README.md
@@ -118,20 +118,20 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/libs/reputation">
|
||||
@bandada/reputation
|
||||
<a href="/libs/credentials">
|
||||
@bandada/credentials
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@bandada/reputation">
|
||||
<img src="https://img.shields.io/npm/v/@bandada/reputation.svg?style=flat-square" alt="NPM version" />
|
||||
<a href="https://npmjs.org/package/@bandada/credentials">
|
||||
<img src="https://img.shields.io/npm/v/@bandada/credentials.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@bandada/reputation">
|
||||
<img src="https://img.shields.io/npm/dm/@bandada/reputation.svg?style=flat-square" alt="Downloads" />
|
||||
<a href="https://npmjs.org/package/@bandada/credentials">
|
||||
<img src="https://img.shields.io/npm/dm/@bandada/credentials.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -300,11 +300,11 @@ Below are the ENV variables used by the `api`:
|
||||
| INFURA_API_KEY | API Key for Infura. This is used for executing blockchain transactions. |
|
||||
| BACKEND_PRIVATE_KEY | Ethereum wallet private key used for making blockchain transactions. |
|
||||
| SIWE_STATEMENT | Statement used as a SIWE message. |
|
||||
| GITHUB_CLIENT_ID | Github client id used for reputation groups. |
|
||||
| GITHUB_CLIENT_SECRET | Github client secret used for reputation groups. |
|
||||
| TWITTER_REDIRECT_URI | Twitter redirect URL used for reputation groups. |
|
||||
| TWITTER_CLIENT_ID | Twitter client id used for reputation groups. |
|
||||
| TWITTER_CLIENT_SECRET | Twitter client secret used for reputation groups. |
|
||||
| GITHUB_CLIENT_ID | Github client id used for credential groups. |
|
||||
| GITHUB_CLIENT_SECRET | Github client secret used for credential groups. |
|
||||
| TWITTER_REDIRECT_URI | Twitter redirect URL used for credential groups. |
|
||||
| TWITTER_CLIENT_ID | Twitter client id used for credential groups. |
|
||||
| TWITTER_CLIENT_SECRET | Twitter client secret used for credential groups. |
|
||||
|
||||
## API
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"build": "nest build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bandada/reputation": "0.12.0",
|
||||
"@bandada/credentials": "0.12.0",
|
||||
"@bandada/utils": "0.12.0",
|
||||
"@ethersproject/hash": "^5.7.0",
|
||||
"@nestjs/common": "^9.0.0",
|
||||
|
||||
@@ -11,7 +11,7 @@ import { AdminsModule } from "./admins/admins.module"
|
||||
import { AuthModule } from "./auth/auth.module"
|
||||
import { GroupsModule } from "./groups/groups.module"
|
||||
import { InvitesModule } from "./invites/invites.module"
|
||||
import { ReputationModule } from "./reputation/reputation.module"
|
||||
import { CredentialsModule } from "./credentials/credentials.module"
|
||||
|
||||
type DB_TYPE = "mysql" | "sqlite" | "postgres"
|
||||
|
||||
@@ -21,7 +21,7 @@ type DB_TYPE = "mysql" | "sqlite" | "postgres"
|
||||
AdminsModule,
|
||||
InvitesModule,
|
||||
GroupsModule,
|
||||
ReputationModule,
|
||||
CredentialsModule,
|
||||
ThrottlerModule.forRoot({
|
||||
ttl: 60,
|
||||
limit: 10
|
||||
|
||||
@@ -2,22 +2,22 @@ import { Body, Controller, Post } from "@nestjs/common"
|
||||
import { ApiExcludeController } from "@nestjs/swagger"
|
||||
import { AddMemberDto } from "./dto/add-member.dto"
|
||||
import { OAuthStateDto } from "./dto/oauth-state.dto"
|
||||
import { ReputationService } from "./reputation.service"
|
||||
import { CredentialsService } from "./credentials.service"
|
||||
|
||||
@ApiExcludeController()
|
||||
@Controller("reputation")
|
||||
export class ReputationController {
|
||||
constructor(private readonly reputationService: ReputationService) {}
|
||||
@Controller("credentials")
|
||||
export class CredentialsController {
|
||||
constructor(private readonly credentialsService: CredentialsService) {}
|
||||
|
||||
@Post("oauth-state")
|
||||
async setOAuthState(@Body() dto: OAuthStateDto): Promise<string> {
|
||||
return this.reputationService.setOAuthState(dto)
|
||||
return this.credentialsService.setOAuthState(dto)
|
||||
}
|
||||
|
||||
@Post()
|
||||
async addMemberByReputation(
|
||||
async addMemberByCredentials(
|
||||
@Body() dto: AddMemberDto
|
||||
): Promise<void | any> {
|
||||
return this.reputationService.addMember(dto.oAuthCode, dto.oAuthState)
|
||||
return this.credentialsService.addMember(dto.oAuthCode, dto.oAuthState)
|
||||
}
|
||||
}
|
||||
19
apps/api/src/app/credentials/credentials.module.ts
Normal file
19
apps/api/src/app/credentials/credentials.module.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { forwardRef, Module } from "@nestjs/common"
|
||||
import { ScheduleModule } from "@nestjs/schedule"
|
||||
import { TypeOrmModule } from "@nestjs/typeorm"
|
||||
import { GroupsModule } from "../groups/groups.module"
|
||||
import { OAuthAccount } from "./entities/credentials-account.entity"
|
||||
import { CredentialsController } from "./credentials.controller"
|
||||
import { CredentialsService } from "./credentials.service"
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ScheduleModule.forRoot(),
|
||||
forwardRef(() => GroupsModule),
|
||||
TypeOrmModule.forFeature([OAuthAccount])
|
||||
],
|
||||
controllers: [CredentialsController],
|
||||
providers: [CredentialsService],
|
||||
exports: [CredentialsService]
|
||||
})
|
||||
export class CredentialsModule {}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getProvider, validateReputation } from "@bandada/reputation"
|
||||
import { getProvider, validateCredentials } from "@bandada/credentials"
|
||||
import { ScheduleModule } from "@nestjs/schedule"
|
||||
import { Test } from "@nestjs/testing"
|
||||
import { TypeOrmModule } from "@nestjs/typeorm"
|
||||
@@ -7,8 +7,8 @@ import { Member } from "../groups/entities/member.entity"
|
||||
import { GroupsService } from "../groups/groups.service"
|
||||
import { Invite } from "../invites/entities/invite.entity"
|
||||
import { InvitesService } from "../invites/invites.service"
|
||||
import { ReputationAccount } from "./entities/reputation-account.entity"
|
||||
import { ReputationService } from "./reputation.service"
|
||||
import { OAuthAccount } from "./entities/credentials-account.entity"
|
||||
import { CredentialsService } from "./credentials.service"
|
||||
|
||||
jest.mock("@bandada/utils", () => ({
|
||||
__esModule: true,
|
||||
@@ -21,7 +21,7 @@ jest.mock("@bandada/utils", () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
jest.mock("@bandada/reputation", () => ({
|
||||
jest.mock("@bandada/credentials", () => ({
|
||||
__esModule: true,
|
||||
getProvider: jest.fn(() => ({
|
||||
getAccessToken: () => "123",
|
||||
@@ -29,12 +29,12 @@ jest.mock("@bandada/reputation", () => ({
|
||||
id: "id"
|
||||
})
|
||||
})),
|
||||
validateReputation: jest.fn(() => true)
|
||||
validateCredentials: jest.fn(() => true)
|
||||
}))
|
||||
|
||||
describe("ReputationService", () => {
|
||||
describe("CredentialsService", () => {
|
||||
let groupsService: GroupsService
|
||||
let reputationService: ReputationService
|
||||
let credentialsService: CredentialsService
|
||||
let groupId: string
|
||||
let stateId: string
|
||||
|
||||
@@ -46,23 +46,18 @@ describe("ReputationService", () => {
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
dropSchema: true,
|
||||
entities: [Group, Invite, Member, ReputationAccount],
|
||||
entities: [Group, Invite, Member, OAuthAccount],
|
||||
synchronize: true
|
||||
})
|
||||
}),
|
||||
TypeOrmModule.forFeature([
|
||||
Group,
|
||||
Invite,
|
||||
Member,
|
||||
ReputationAccount
|
||||
]),
|
||||
TypeOrmModule.forFeature([Group, Invite, Member, OAuthAccount]),
|
||||
ScheduleModule.forRoot()
|
||||
],
|
||||
providers: [GroupsService, InvitesService, ReputationService]
|
||||
providers: [GroupsService, InvitesService, CredentialsService]
|
||||
}).compile()
|
||||
|
||||
groupsService = await module.resolve(GroupsService)
|
||||
reputationService = await module.resolve(ReputationService)
|
||||
credentialsService = await module.resolve(CredentialsService)
|
||||
|
||||
const { id } = await groupsService.createGroup(
|
||||
{
|
||||
@@ -70,7 +65,7 @@ describe("ReputationService", () => {
|
||||
description: "This is a description",
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600,
|
||||
reputationCriteria: JSON.stringify({
|
||||
credentials: JSON.stringify({
|
||||
id: "GITHUB_FOLLOWERS",
|
||||
criteria: {
|
||||
minFollowers: 12
|
||||
@@ -84,7 +79,7 @@ describe("ReputationService", () => {
|
||||
})
|
||||
|
||||
describe("# setOAuthState", () => {
|
||||
it("Should throw an error if the group is not a reputation group", async () => {
|
||||
it("Should throw an error if the group is not a credential group", async () => {
|
||||
const { id: _groupId } = await groupsService.createGroup(
|
||||
{
|
||||
name: "Group2",
|
||||
@@ -95,19 +90,19 @@ describe("ReputationService", () => {
|
||||
"admin"
|
||||
)
|
||||
|
||||
const fun = reputationService.setOAuthState({
|
||||
const fun = credentialsService.setOAuthState({
|
||||
groupId: _groupId,
|
||||
memberId: "123",
|
||||
providerName: "twitter"
|
||||
})
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Group with id '${_groupId}' is not a reputation group`
|
||||
`Group with id '${_groupId}' is not a credential group`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should throw an error if the provider is not supported", async () => {
|
||||
const fun = reputationService.setOAuthState({
|
||||
const fun = credentialsService.setOAuthState({
|
||||
groupId,
|
||||
memberId: "123",
|
||||
providerName: "reddit"
|
||||
@@ -119,7 +114,7 @@ describe("ReputationService", () => {
|
||||
})
|
||||
|
||||
it("Should create and save a state id", async () => {
|
||||
stateId = await reputationService.setOAuthState({
|
||||
stateId = await credentialsService.setOAuthState({
|
||||
groupId,
|
||||
memberId: "123",
|
||||
providerName: "twitter"
|
||||
@@ -139,15 +134,15 @@ describe("ReputationService", () => {
|
||||
} as any)
|
||||
)
|
||||
|
||||
const _stateId = await reputationService.setOAuthState({
|
||||
const _stateId = await credentialsService.setOAuthState({
|
||||
groupId,
|
||||
memberId: "123",
|
||||
providerName: "twitter"
|
||||
})
|
||||
|
||||
await reputationService.addMember("code", _stateId)
|
||||
await credentialsService.addMember("code", _stateId)
|
||||
|
||||
const fun = reputationService.setOAuthState({
|
||||
const fun = credentialsService.setOAuthState({
|
||||
groupId,
|
||||
memberId: "123",
|
||||
providerName: "twitter"
|
||||
@@ -161,13 +156,13 @@ describe("ReputationService", () => {
|
||||
|
||||
describe("# addMember", () => {
|
||||
it("Should throw an error if the OAuth does not exist", async () => {
|
||||
const fun = reputationService.addMember("code", "123")
|
||||
const fun = credentialsService.addMember("code", "123")
|
||||
|
||||
await expect(fun).rejects.toThrow(`OAuth state does not exist`)
|
||||
})
|
||||
|
||||
it("Should add a member to a reputation group", async () => {
|
||||
const clientRedirectUri = await reputationService.addMember(
|
||||
it("Should add a member to a credential group", async () => {
|
||||
const clientRedirectUri = await credentialsService.addMember(
|
||||
"code",
|
||||
stateId
|
||||
)
|
||||
@@ -176,20 +171,20 @@ describe("ReputationService", () => {
|
||||
})
|
||||
|
||||
it("Should throw an error if the same OAuth account tries to join the same group", async () => {
|
||||
const _stateId = await reputationService.setOAuthState({
|
||||
const _stateId = await credentialsService.setOAuthState({
|
||||
groupId,
|
||||
memberId: "124",
|
||||
providerName: "twitter"
|
||||
})
|
||||
|
||||
const fun = reputationService.addMember("code", _stateId)
|
||||
const fun = credentialsService.addMember("code", _stateId)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`OAuth account has already joined the group`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should throw an error if the OAuth account does not have enough reputation", async () => {
|
||||
it("Should throw an error if the OAuth account does not have enough credential", async () => {
|
||||
;(getProvider as any).mockImplementationOnce(
|
||||
() =>
|
||||
({
|
||||
@@ -199,20 +194,20 @@ describe("ReputationService", () => {
|
||||
}))
|
||||
} as any)
|
||||
)
|
||||
;(validateReputation as any).mockImplementationOnce(
|
||||
;(validateCredentials as any).mockImplementationOnce(
|
||||
async () => false
|
||||
)
|
||||
|
||||
const _stateId = await reputationService.setOAuthState({
|
||||
const _stateId = await credentialsService.setOAuthState({
|
||||
groupId,
|
||||
memberId: "124",
|
||||
providerName: "twitter"
|
||||
})
|
||||
|
||||
const fun = reputationService.addMember("code", _stateId)
|
||||
const fun = credentialsService.addMember("code", _stateId)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`OAuth account does not match reputation criteria`
|
||||
`OAuth account does not match credential criteria`
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { validateReputation, getProvider } from "@bandada/reputation"
|
||||
import { validateCredentials, getProvider } from "@bandada/credentials"
|
||||
import { id } from "@ethersproject/hash"
|
||||
import {
|
||||
BadRequestException,
|
||||
@@ -11,16 +11,16 @@ import { InjectRepository } from "@nestjs/typeorm"
|
||||
import { Repository } from "typeorm"
|
||||
import { v4 } from "uuid"
|
||||
import { GroupsService } from "../groups/groups.service"
|
||||
import { ReputationAccount } from "./entities/reputation-account.entity"
|
||||
import { OAuthAccount } from "./entities/credentials-account.entity"
|
||||
import { OAuthState } from "./types"
|
||||
|
||||
@Injectable()
|
||||
export class ReputationService {
|
||||
export class CredentialsService {
|
||||
private oAuthState: Map<string, OAuthState>
|
||||
|
||||
constructor(
|
||||
@InjectRepository(ReputationAccount)
|
||||
private readonly reputationAccountRepository: Repository<ReputationAccount>,
|
||||
@InjectRepository(OAuthAccount)
|
||||
private readonly oAuthAccountRepository: Repository<OAuthAccount>,
|
||||
@Inject(forwardRef(() => GroupsService))
|
||||
private readonly groupsService: GroupsService
|
||||
) {
|
||||
@@ -35,9 +35,9 @@ export class ReputationService {
|
||||
async setOAuthState(oAuthState: OAuthState): Promise<string> {
|
||||
const group = await this.groupsService.getGroup(oAuthState.groupId)
|
||||
|
||||
if (!group.reputationCriteria) {
|
||||
if (!group.credentials) {
|
||||
throw new BadRequestException(
|
||||
`Group with id '${oAuthState.groupId}' is not a reputation group`
|
||||
`Group with id '${oAuthState.groupId}' is not a credential group`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export class ReputationService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a member to the reputation group if they meet the right reputation criteria.
|
||||
* Add a member to the credential group if they meet the right credential criteria.
|
||||
* @param oAuthCode OAuth code to exchange for an access token.
|
||||
* @param OAuthState OAuth state to prevent forgery attacks.
|
||||
* @returns Redirect URI
|
||||
@@ -107,7 +107,7 @@ export class ReputationService {
|
||||
const accountHash = id(profile.id + groupId)
|
||||
|
||||
if (
|
||||
group.reputationAccounts.find(
|
||||
group.oAuthAccounts.find(
|
||||
(account) => account.accountHash === accountHash
|
||||
)
|
||||
) {
|
||||
@@ -116,26 +116,26 @@ export class ReputationService {
|
||||
)
|
||||
}
|
||||
|
||||
// Check reputation.
|
||||
// Check credentials.
|
||||
if (
|
||||
!(await validateReputation(JSON.parse(group.reputationCriteria), {
|
||||
!(await validateCredentials(JSON.parse(group.credentials), {
|
||||
profile,
|
||||
accessTokens: { [providerName]: accessToken }
|
||||
}))
|
||||
) {
|
||||
throw new UnauthorizedException(
|
||||
"OAuth account does not match reputation criteria"
|
||||
"OAuth account does not match credentials"
|
||||
)
|
||||
}
|
||||
|
||||
// Save OAuth account to prevent the same account to join groups with
|
||||
// different member ids.
|
||||
const reputationAccount = new ReputationAccount()
|
||||
const oAuthAccount = new OAuthAccount()
|
||||
|
||||
reputationAccount.group = group
|
||||
reputationAccount.accountHash = accountHash
|
||||
oAuthAccount.group = group
|
||||
oAuthAccount.accountHash = accountHash
|
||||
|
||||
await this.reputationAccountRepository.save(reputationAccount)
|
||||
await this.oAuthAccountRepository.save(oAuthAccount)
|
||||
await this.groupsService.addMember(groupId, memberId)
|
||||
|
||||
this.oAuthState.delete(oAuthState)
|
||||
@@ -8,14 +8,14 @@ import {
|
||||
} from "typeorm"
|
||||
import { Group } from "../../groups/entities/group.entity"
|
||||
|
||||
@Entity("reputation_accounts")
|
||||
@Entity("oauth_accounts")
|
||||
@Index(["accountHash", "group"])
|
||||
@Unique(["accountHash", "group"])
|
||||
export class ReputationAccount {
|
||||
export class OAuthAccount {
|
||||
@PrimaryColumn()
|
||||
accountHash: string
|
||||
|
||||
@ManyToOne(() => Group, (group) => group.reputationAccounts, {
|
||||
@ManyToOne(() => Group, (group) => group.oAuthAccounts, {
|
||||
onDelete: "CASCADE"
|
||||
})
|
||||
@JoinColumn({ name: "group_id" })
|
||||
@@ -18,5 +18,5 @@ export class Group {
|
||||
@ApiProperty({ isArray: true })
|
||||
members: string
|
||||
@ApiProperty()
|
||||
reputationCriteria: object
|
||||
credentials: object
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export class GroupResponse {
|
||||
@ApiProperty()
|
||||
fingerprintDuration: number
|
||||
@ApiProperty()
|
||||
reputationCriteria: object
|
||||
credentials: object
|
||||
@ApiProperty()
|
||||
apiEnabled: boolean
|
||||
@ApiProperty()
|
||||
|
||||
@@ -38,5 +38,5 @@ export class CreateGroupDto {
|
||||
|
||||
@IsJSON()
|
||||
@IsOptional()
|
||||
readonly reputationCriteria?: any
|
||||
readonly credentials?: any
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@ export class UpdateGroupDto {
|
||||
|
||||
@IsOptional()
|
||||
@IsJSON()
|
||||
readonly reputationCriteria?: any
|
||||
readonly credentials?: any
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PrimaryColumn,
|
||||
UpdateDateColumn
|
||||
} from "typeorm"
|
||||
import { ReputationAccount } from "../../reputation/entities/reputation-account.entity"
|
||||
import { OAuthAccount } from "../../credentials/entities/credentials-account.entity"
|
||||
import { Member } from "./member.entity"
|
||||
|
||||
@Entity("groups")
|
||||
@@ -36,17 +36,17 @@ export class Group {
|
||||
})
|
||||
members: Member[]
|
||||
|
||||
@OneToMany(() => ReputationAccount, (account) => account.group, {
|
||||
@OneToMany(() => OAuthAccount, (account) => account.group, {
|
||||
cascade: ["insert"]
|
||||
})
|
||||
reputationAccounts: ReputationAccount[]
|
||||
oAuthAccounts: OAuthAccount[]
|
||||
|
||||
@Column({
|
||||
type: "simple-json",
|
||||
name: "reputation_criteria",
|
||||
name: "credentials",
|
||||
nullable: true
|
||||
})
|
||||
reputationCriteria: any // TODO: Add correct type for reputationCriteria JSON
|
||||
credentials: any // TODO: Add correct type for credentials JSON
|
||||
|
||||
@Column({ name: "api_enabled", default: false })
|
||||
apiEnabled: boolean
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Test } from "@nestjs/testing"
|
||||
import { TypeOrmModule } from "@nestjs/typeorm"
|
||||
import { Invite } from "../invites/entities/invite.entity"
|
||||
import { InvitesService } from "../invites/invites.service"
|
||||
import { ReputationAccount } from "../reputation/entities/reputation-account.entity"
|
||||
import { OAuthAccount } from "../credentials/entities/credentials-account.entity"
|
||||
import { Group } from "./entities/group.entity"
|
||||
import { Member } from "./entities/member.entity"
|
||||
import { GroupsService } from "./groups.service"
|
||||
@@ -33,7 +33,7 @@ describe("GroupsService", () => {
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
dropSchema: true,
|
||||
entities: [Group, Invite, Member, ReputationAccount],
|
||||
entities: [Group, Invite, Member, OAuthAccount],
|
||||
synchronize: true
|
||||
})
|
||||
}),
|
||||
@@ -112,7 +112,7 @@ describe("GroupsService", () => {
|
||||
description: "This is a description",
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600,
|
||||
reputationCriteria: {
|
||||
credentials: {
|
||||
id: "GITHUB_FOLLOWERS",
|
||||
criteria: {
|
||||
minFollowers: 12
|
||||
@@ -122,13 +122,13 @@ describe("GroupsService", () => {
|
||||
"admin"
|
||||
)
|
||||
|
||||
const { description, fingerprintDuration, reputationCriteria } =
|
||||
const { description, fingerprintDuration, credentials } =
|
||||
await groupsService.updateGroup(
|
||||
id,
|
||||
{
|
||||
description: "This is a new description",
|
||||
fingerprintDuration: 1000,
|
||||
reputationCriteria: {
|
||||
credentials: {
|
||||
id: "TWITTER_FOLLOWERS",
|
||||
minFollowers: 23
|
||||
}
|
||||
@@ -138,7 +138,7 @@ describe("GroupsService", () => {
|
||||
|
||||
expect(description).toContain("new")
|
||||
expect(fingerprintDuration).toBe(1000)
|
||||
expect(reputationCriteria.id).toBe("TWITTER_FOLLOWERS")
|
||||
expect(credentials.id).toBe("TWITTER_FOLLOWERS")
|
||||
})
|
||||
|
||||
it("Should not update a group if the admin is the wrong one", async () => {
|
||||
|
||||
@@ -63,7 +63,7 @@ export class GroupsService {
|
||||
description,
|
||||
treeDepth,
|
||||
fingerprintDuration,
|
||||
reputationCriteria
|
||||
credentials
|
||||
}: CreateGroupDto,
|
||||
adminId: string
|
||||
): Promise<Group> {
|
||||
@@ -79,7 +79,7 @@ export class GroupsService {
|
||||
description,
|
||||
treeDepth,
|
||||
fingerprintDuration,
|
||||
reputationCriteria,
|
||||
credentials,
|
||||
adminId,
|
||||
members: []
|
||||
})
|
||||
@@ -133,7 +133,7 @@ export class GroupsService {
|
||||
description,
|
||||
treeDepth,
|
||||
apiEnabled,
|
||||
reputationCriteria,
|
||||
credentials,
|
||||
fingerprintDuration
|
||||
}: UpdateGroupDto,
|
||||
adminId: string
|
||||
@@ -166,11 +166,11 @@ export class GroupsService {
|
||||
this._updateContractGroup(cachedGroup)
|
||||
}
|
||||
|
||||
if (group.reputationCriteria && reputationCriteria) {
|
||||
group.reputationCriteria = reputationCriteria
|
||||
if (group.credentials && credentials) {
|
||||
group.credentials = credentials
|
||||
}
|
||||
|
||||
if (!group.reputationCriteria && apiEnabled !== undefined) {
|
||||
if (!group.credentials && apiEnabled !== undefined) {
|
||||
group.apiEnabled = apiEnabled
|
||||
|
||||
// Generate a new API key if it doesn't exist
|
||||
@@ -437,7 +437,7 @@ export class GroupsService {
|
||||
*/
|
||||
async getGroup(groupId: string): Promise<Group> {
|
||||
const group = await this.groupRepository.findOne({
|
||||
relations: { members: true, reputationAccounts: true },
|
||||
relations: { members: true, oAuthAccounts: true },
|
||||
where: { id: groupId }
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export function mapGroupToResponseDTO(
|
||||
fingerprintDuration: group.fingerprintDuration,
|
||||
createdAt: group.createdAt,
|
||||
members: (group.members || []).map((m) => m.id),
|
||||
reputationCriteria: group.reputationCriteria,
|
||||
credentials: group.credentials,
|
||||
apiKey: undefined,
|
||||
apiEnabled: undefined
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { TypeOrmModule } from "@nestjs/typeorm"
|
||||
import { Group } from "../groups/entities/group.entity"
|
||||
import { Member } from "../groups/entities/member.entity"
|
||||
import { GroupsService } from "../groups/groups.service"
|
||||
import { ReputationAccount } from "../reputation/entities/reputation-account.entity"
|
||||
import { OAuthAccount } from "../credentials/entities/credentials-account.entity"
|
||||
import { Invite } from "./entities/invite.entity"
|
||||
import { InvitesService } from "./invites.service"
|
||||
|
||||
@@ -32,7 +32,7 @@ describe("InvitesService", () => {
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
dropSchema: true,
|
||||
entities: [Group, Invite, Member, ReputationAccount],
|
||||
entities: [Group, Invite, Member, OAuthAccount],
|
||||
synchronize: true
|
||||
})
|
||||
}),
|
||||
@@ -77,14 +77,14 @@ describe("InvitesService", () => {
|
||||
await expect(fun).rejects.toThrow("You are not the admin")
|
||||
})
|
||||
|
||||
it("Should not create an invite if the group is a reputation group", async () => {
|
||||
it("Should not create an invite if the group is a credential group", async () => {
|
||||
const group = await groupsService.createGroup(
|
||||
{
|
||||
name: "Group2",
|
||||
description: "This is a description",
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600,
|
||||
reputationCriteria: {
|
||||
credentials: {
|
||||
id: "GITHUB_FOLLOWERS",
|
||||
criteria: {
|
||||
minFollowers: 12
|
||||
@@ -100,7 +100,7 @@ describe("InvitesService", () => {
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
"Reputation groups cannot be accessed via invites"
|
||||
"Credential groups cannot be accessed via invites"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -40,9 +40,9 @@ export class InvitesService {
|
||||
)
|
||||
}
|
||||
|
||||
if (group.reputationCriteria) {
|
||||
if (group.credentials) {
|
||||
throw new UnauthorizedException(
|
||||
"Reputation groups cannot be accessed via invites"
|
||||
"Credential groups cannot be accessed via invites"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { forwardRef, Module } from "@nestjs/common"
|
||||
import { ScheduleModule } from "@nestjs/schedule"
|
||||
import { TypeOrmModule } from "@nestjs/typeorm"
|
||||
import { GroupsModule } from "../groups/groups.module"
|
||||
import { ReputationAccount } from "./entities/reputation-account.entity"
|
||||
import { ReputationController } from "./reputation.controller"
|
||||
import { ReputationService } from "./reputation.service"
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ScheduleModule.forRoot(),
|
||||
forwardRef(() => GroupsModule),
|
||||
TypeOrmModule.forFeature([ReputationAccount])
|
||||
],
|
||||
controllers: [ReputationController],
|
||||
providers: [ReputationService],
|
||||
exports: [ReputationService]
|
||||
})
|
||||
export class ReputationModule {}
|
||||
@@ -1,8 +1,9 @@
|
||||
# This file contains build time variables for dev env.
|
||||
|
||||
VITE_API_URL=http://localhost:3000
|
||||
VITE_CLIENT_URL=http://localhost:3002
|
||||
VITE_CLIENT_INVITES_URL=http://localhost:3002?inviteCode=\
|
||||
VITE_ETHEREUM_NETWORK=goerli
|
||||
VITE_GITHUB_CLIENT_ID=a83a8b014ef38270fb22
|
||||
VITE_TWITTER_CLIENT_ID=NV82Mm85NWlSZ1llZkpLMl9vN3A6MTpjaQ
|
||||
VITE_TWITTER_REDIRECT_URI=http://localhost:3001/reputation
|
||||
VITE_TWITTER_REDIRECT_URI=http://localhost:3001/credentials
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# This file contains build time variables for prod env.
|
||||
|
||||
VITE_API_URL=https://api.bandada.pse.dev
|
||||
VITE_CLIENT_URL=https://client.bandada.pse.dev
|
||||
VITE_CLIENT_INVITES_URL=https://client.bandada.pse.dev?inviteCode=\
|
||||
VITE_ETHEREUM_NETWORK=goerli
|
||||
VITE_GITHUB_CLIENT_ID=6ccd7b93e84260e353f9
|
||||
VITE_TWITTER_CLIENT_ID=NV82Mm85NWlSZ1llZkpLMl9vN3A6MTpjaQ
|
||||
VITE_TWITTER_REDIRECT_URI=https://bandada.pse.dev/reputation
|
||||
VITE_TWITTER_REDIRECT_URI=https://bandada.pse.dev/credentials
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bandada/reputation": "0.12.0",
|
||||
"@bandada/credentials": "0.12.0",
|
||||
"@bandada/utils": "0.12.0",
|
||||
"@chakra-ui/react": "^2.5.1",
|
||||
"@chakra-ui/theme-tools": "^2.0.16",
|
||||
|
||||
@@ -101,7 +101,7 @@ export async function createGroup(
|
||||
description: string,
|
||||
treeDepth: number,
|
||||
fingerprintDuration: number,
|
||||
reputationCriteria?: any
|
||||
credentials?: any
|
||||
): Promise<Group | null> {
|
||||
try {
|
||||
const group = await request(`${API_URL}/groups`, {
|
||||
@@ -111,7 +111,7 @@ export async function createGroup(
|
||||
description,
|
||||
treeDepth,
|
||||
fingerprintDuration,
|
||||
reputationCriteria: JSON.stringify(reputationCriteria)
|
||||
credentials: JSON.stringify(credentials)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -205,7 +205,7 @@ export async function removeGroup(groupId: string): Promise<void | null> {
|
||||
/**
|
||||
* It returns a random string to be used as a OAuth state, to to protect against
|
||||
* forgery attacks. It will be used to retrieve group, member, redirectURI and provider
|
||||
* before checking reputation and adding members.
|
||||
* before checking credentials and adding members.
|
||||
* @param group The group id.
|
||||
* @param memberId The group member id.
|
||||
* @param redirectUri The URL where clients will be sent after authorization.
|
||||
@@ -219,7 +219,7 @@ export async function setOAuthState(
|
||||
redirectUri?: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
return await request(`${API_URL}/reputation/oauth-state`, {
|
||||
return await request(`${API_URL}/credentials/oauth-state`, {
|
||||
method: "POST",
|
||||
data: {
|
||||
groupId,
|
||||
@@ -242,16 +242,16 @@ export async function setOAuthState(
|
||||
}
|
||||
|
||||
/**
|
||||
* It adds a new member to an existing reputation group.
|
||||
* It adds a new member to an existing credentials group.
|
||||
* @param oAuthState The OAuth state.
|
||||
* @param oAuthCode The OAuth code.
|
||||
*/
|
||||
export async function addMemberByReputation(
|
||||
export async function addMemberByCredentials(
|
||||
oAuthState: string,
|
||||
oAuthCode: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
return await request(`${API_URL}/reputation`, {
|
||||
return await request(`${API_URL}/credentials`, {
|
||||
method: "POST",
|
||||
data: {
|
||||
oAuthCode,
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
AbsoluteCenter,
|
||||
Box,
|
||||
Button,
|
||||
Code,
|
||||
Divider,
|
||||
Heading,
|
||||
Icon,
|
||||
@@ -40,15 +39,23 @@ export default function AddMemberModal({
|
||||
const [_isLoading, setIsLoading] = useState(false)
|
||||
const {
|
||||
hasCopied,
|
||||
value: _inviteLink,
|
||||
setValue: setInviteLink,
|
||||
value: _clientLink,
|
||||
setValue: setClientLink,
|
||||
onCopy
|
||||
} = useClipboard("")
|
||||
const { data: signer } = useSigner()
|
||||
|
||||
useEffect(() => {
|
||||
setMemberId("")
|
||||
}, [group, isOpen, setInviteLink])
|
||||
|
||||
if (group.credentials) {
|
||||
setClientLink(
|
||||
`${import.meta.env.VITE_CLIENT_URL}?credentialGroupId=${
|
||||
group.id
|
||||
}`
|
||||
)
|
||||
}
|
||||
}, [group, setClientLink])
|
||||
|
||||
const addMember = useCallback(async () => {
|
||||
if (!_memberId) {
|
||||
@@ -105,8 +112,8 @@ export default function AddMemberModal({
|
||||
return
|
||||
}
|
||||
|
||||
setInviteLink(inviteLink)
|
||||
}, [group, setInviteLink])
|
||||
setClientLink(inviteLink)
|
||||
}, [group, setClientLink])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -122,7 +129,7 @@ export default function AddMemberModal({
|
||||
New member
|
||||
</Heading>
|
||||
|
||||
{!group.reputationCriteria && (
|
||||
{!group.credentials && (
|
||||
<Box mb="5px">
|
||||
<Text my="10px" color="balticSea.800">
|
||||
Add member ID
|
||||
@@ -150,85 +157,75 @@ export default function AddMemberModal({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{group.type === "off-chain" &&
|
||||
!group.reputationCriteria && (
|
||||
<>
|
||||
<Box position="relative" py="8">
|
||||
<Divider borderColor="balticSea.300" />
|
||||
<AbsoluteCenter
|
||||
fontSize="13px"
|
||||
px="4"
|
||||
bgColor="balticSea.50"
|
||||
>
|
||||
OR
|
||||
</AbsoluteCenter>
|
||||
</Box>
|
||||
{group.type === "off-chain" && !group.credentials && (
|
||||
<Box position="relative" py="8">
|
||||
<Divider borderColor="balticSea.300" />
|
||||
<AbsoluteCenter
|
||||
fontSize="13px"
|
||||
px="4"
|
||||
bgColor="balticSea.50"
|
||||
>
|
||||
OR
|
||||
</AbsoluteCenter>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box mb="30px">
|
||||
<Text mb="10px" color="balticSea.800">
|
||||
Share invite link
|
||||
</Text>
|
||||
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
pr="50px"
|
||||
placeholder="Invite link"
|
||||
value={_inviteLink}
|
||||
isDisabled
|
||||
/>
|
||||
<InputRightElement mr="5px">
|
||||
<Tooltip
|
||||
label={
|
||||
hasCopied
|
||||
? "Copied!"
|
||||
: "Copy"
|
||||
}
|
||||
closeOnClick={false}
|
||||
hasArrow
|
||||
>
|
||||
<IconButton
|
||||
variant="link"
|
||||
aria-label="Copy invite link"
|
||||
onClick={onCopy}
|
||||
onMouseDown={(e) =>
|
||||
e.preventDefault()
|
||||
}
|
||||
icon={
|
||||
<Icon
|
||||
color="sunsetOrange.600"
|
||||
boxSize="5"
|
||||
as={FiCopy}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
|
||||
<Button
|
||||
mt="10px"
|
||||
variant="link"
|
||||
color="balticSea.600"
|
||||
textDecoration="underline"
|
||||
onClick={generateInviteLink}
|
||||
>
|
||||
Generate new link
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{group.reputationCriteria && (
|
||||
<>
|
||||
<Text mb="10px">
|
||||
To allow users to join your group, you can use
|
||||
the following Bandada URL:
|
||||
{group.type === "off-chain" && (
|
||||
<Box mb="30px">
|
||||
<Text mb="10px" color="balticSea.800">
|
||||
{!group.credentials
|
||||
? "Share invite link"
|
||||
: "Share access link"}
|
||||
</Text>
|
||||
<Code
|
||||
p="3"
|
||||
mb="20px"
|
||||
>{`${window.location.origin}/reputation?group=<groupID>&member=<memberID>&provider=<providerName>&redirect_uri=<redirectURI>`}</Code>
|
||||
</>
|
||||
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
pr="50px"
|
||||
placeholder={
|
||||
!group.credentials
|
||||
? "Invite link"
|
||||
: "Access link"
|
||||
}
|
||||
value={_clientLink}
|
||||
isDisabled
|
||||
/>
|
||||
<InputRightElement mr="5px">
|
||||
<Tooltip
|
||||
label={hasCopied ? "Copied!" : "Copy"}
|
||||
closeOnClick={false}
|
||||
hasArrow
|
||||
>
|
||||
<IconButton
|
||||
variant="link"
|
||||
aria-label="Copy invite link"
|
||||
onClick={onCopy}
|
||||
onMouseDown={(e) =>
|
||||
e.preventDefault()
|
||||
}
|
||||
icon={
|
||||
<Icon
|
||||
color="sunsetOrange.600"
|
||||
boxSize="5"
|
||||
as={FiCopy}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
|
||||
{!group.credentials && (
|
||||
<Button
|
||||
mt="10px"
|
||||
variant="link"
|
||||
color="balticSea.600"
|
||||
textDecoration="underline"
|
||||
onClick={generateInviteLink}
|
||||
>
|
||||
Generate new link
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { validators } from "@bandada/reputation"
|
||||
import { validators } from "@bandada/credentials"
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -38,10 +38,10 @@ export default function AccessModeStep({
|
||||
}: AccessModeStepProps): JSX.Element {
|
||||
const [_accessMode, setAccessMode] = useState<AccessMode>("manual")
|
||||
const [_validator, setValidator] = useState<number>(0)
|
||||
const [_reputationCriteria, setReputationCriteria] = useState<any>()
|
||||
const [_credentials, setCredentials] = useState<any>()
|
||||
|
||||
useEffect(() => {
|
||||
setReputationCriteria({
|
||||
setCredentials({
|
||||
id: validators[_validator].id,
|
||||
criteria: {}
|
||||
})
|
||||
@@ -49,9 +49,9 @@ export default function AccessModeStep({
|
||||
|
||||
useEffect(() => {
|
||||
if (_accessMode === "manual") {
|
||||
setReputationCriteria(undefined)
|
||||
setCredentials(undefined)
|
||||
} else {
|
||||
setReputationCriteria({
|
||||
setCredentials({
|
||||
id: validators[_validator].id,
|
||||
criteria: {}
|
||||
})
|
||||
@@ -154,8 +154,8 @@ export default function AccessModeStep({
|
||||
</Select>
|
||||
</VStack>
|
||||
|
||||
{_reputationCriteria &&
|
||||
_reputationCriteria.criteria &&
|
||||
{_credentials &&
|
||||
_credentials.criteria &&
|
||||
Object.entries(validators[_validator].criteriaABI).map(
|
||||
(parameter) => (
|
||||
<VStack
|
||||
@@ -169,15 +169,15 @@ export default function AccessModeStep({
|
||||
<NumberInput
|
||||
size="lg"
|
||||
value={
|
||||
_reputationCriteria.criteria[
|
||||
_credentials.criteria[
|
||||
parameter[0]
|
||||
]
|
||||
}
|
||||
onChange={(value) =>
|
||||
setReputationCriteria({
|
||||
..._reputationCriteria,
|
||||
setCredentials({
|
||||
..._credentials,
|
||||
criteria: {
|
||||
..._reputationCriteria.criteria,
|
||||
..._credentials.criteria,
|
||||
[parameter[0]]:
|
||||
Number(value)
|
||||
}
|
||||
@@ -194,15 +194,15 @@ export default function AccessModeStep({
|
||||
<Input
|
||||
size="lg"
|
||||
value={
|
||||
_reputationCriteria.criteria[
|
||||
_credentials.criteria[
|
||||
parameter[0]
|
||||
]
|
||||
}
|
||||
onChange={(event) =>
|
||||
setReputationCriteria({
|
||||
..._reputationCriteria,
|
||||
setCredentials({
|
||||
..._credentials,
|
||||
criteria: {
|
||||
..._reputationCriteria.criteria,
|
||||
..._credentials.criteria,
|
||||
[parameter[0]]:
|
||||
event.target.value
|
||||
}
|
||||
@@ -223,7 +223,7 @@ export default function AccessModeStep({
|
||||
>
|
||||
Disclaimer: We will use a bit of your member’s data
|
||||
to check if they meet the criteria and generate
|
||||
their reputation to join the group.
|
||||
their credentials to join the group.
|
||||
</Text>
|
||||
</Box>
|
||||
</>
|
||||
@@ -237,23 +237,22 @@ export default function AccessModeStep({
|
||||
isDisabled={
|
||||
!_accessMode ||
|
||||
(_accessMode === "credentials" &&
|
||||
(!_reputationCriteria ||
|
||||
!_reputationCriteria.criteria ||
|
||||
Object.keys(_reputationCriteria.criteria)
|
||||
.length !==
|
||||
(!_credentials ||
|
||||
!_credentials.criteria ||
|
||||
Object.keys(_credentials.criteria).length !==
|
||||
Object.keys(
|
||||
validators[_validator].criteriaABI
|
||||
).length ||
|
||||
Object.values(
|
||||
_reputationCriteria.criteria
|
||||
).some((c) => c === undefined)))
|
||||
Object.values(_credentials.criteria).some(
|
||||
(c) => c === undefined
|
||||
)))
|
||||
}
|
||||
variant="solid"
|
||||
colorScheme="primary"
|
||||
onClick={() => {
|
||||
onSubmit({
|
||||
...group,
|
||||
reputationCriteria: _reputationCriteria
|
||||
credentials: _credentials
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function FinalPreviewStep({
|
||||
group.description,
|
||||
group.treeDepth,
|
||||
group.fingerprintDuration,
|
||||
group.reputationCriteria
|
||||
group.credentials
|
||||
)
|
||||
|
||||
if (response === null) {
|
||||
|
||||
@@ -234,84 +234,80 @@ export default function GroupPage(): JSX.Element {
|
||||
</Box>
|
||||
</HStack>
|
||||
|
||||
{groupType === "off-chain" &&
|
||||
!_group.reputationCriteria && (
|
||||
<Box
|
||||
bgColor="balticSea.50"
|
||||
p="25px 30px 25px 30px"
|
||||
borderRadius="8px"
|
||||
>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="20px">Use API key</Text>
|
||||
{groupType === "off-chain" && !_group.credentials && (
|
||||
<Box
|
||||
bgColor="balticSea.50"
|
||||
p="25px 30px 25px 30px"
|
||||
borderRadius="8px"
|
||||
>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="20px">Use API key</Text>
|
||||
|
||||
<Switch
|
||||
id="enable-api"
|
||||
isChecked={_group.apiEnabled}
|
||||
onChange={(event) =>
|
||||
onApiAccessToggle(
|
||||
event.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
</HStack>
|
||||
<Switch
|
||||
id="enable-api"
|
||||
isChecked={_group.apiEnabled}
|
||||
onChange={(event) =>
|
||||
onApiAccessToggle(event.target.checked)
|
||||
}
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
<Text mt="10px" color="balticSea.700">
|
||||
Connect your app to your group using an API
|
||||
key.
|
||||
</Text>
|
||||
<Text mt="10px" color="balticSea.700">
|
||||
Connect your app to your group using an API key.
|
||||
</Text>
|
||||
|
||||
{_group.apiEnabled && (
|
||||
<>
|
||||
<InputGroup size="lg" mt="10px">
|
||||
<Input
|
||||
pr="50px"
|
||||
placeholder="API key"
|
||||
value={_group?.apiKey}
|
||||
isDisabled
|
||||
/>
|
||||
{_group.apiEnabled && (
|
||||
<>
|
||||
<InputGroup size="lg" mt="10px">
|
||||
<Input
|
||||
pr="50px"
|
||||
placeholder="API key"
|
||||
value={_group?.apiKey}
|
||||
isDisabled
|
||||
/>
|
||||
|
||||
<InputRightElement mr="5px">
|
||||
<Tooltip
|
||||
label={
|
||||
hasCopied
|
||||
? "Copied!"
|
||||
: "Copy"
|
||||
<InputRightElement mr="5px">
|
||||
<Tooltip
|
||||
label={
|
||||
hasCopied
|
||||
? "Copied!"
|
||||
: "Copy"
|
||||
}
|
||||
closeOnClick={false}
|
||||
hasArrow
|
||||
>
|
||||
<IconButton
|
||||
variant="link"
|
||||
aria-label="Copy invite link"
|
||||
onClick={onCopy}
|
||||
onMouseDown={(e) =>
|
||||
e.preventDefault()
|
||||
}
|
||||
closeOnClick={false}
|
||||
hasArrow
|
||||
>
|
||||
<IconButton
|
||||
variant="link"
|
||||
aria-label="Copy invite link"
|
||||
onClick={onCopy}
|
||||
onMouseDown={(e) =>
|
||||
e.preventDefault()
|
||||
}
|
||||
icon={
|
||||
<Icon
|
||||
color="sunsetOrange.600"
|
||||
boxSize="5"
|
||||
as={FiCopy}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
icon={
|
||||
<Icon
|
||||
color="sunsetOrange.600"
|
||||
boxSize="5"
|
||||
as={FiCopy}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
|
||||
<Button
|
||||
mt="10px"
|
||||
variant="link"
|
||||
color="balticSea.600"
|
||||
textDecoration="underline"
|
||||
onClick={generateApiKey}
|
||||
>
|
||||
Generate new key
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
mt="10px"
|
||||
variant="link"
|
||||
color="balticSea.600"
|
||||
textDecoration="underline"
|
||||
onClick={generateApiKey}
|
||||
>
|
||||
Generate new key
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Image src={image1} />
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { getProvider } from "@bandada/reputation"
|
||||
import { Flex, Text } from "@chakra-ui/react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useSearchParams } from "react-router-dom"
|
||||
import { addMemberByReputation, setOAuthState } from "../api/bandadaAPI"
|
||||
|
||||
export default function ReputationPage() {
|
||||
const [_searchParams] = useSearchParams()
|
||||
const [_message, setMessage] = useState<string>(
|
||||
"Joining reputation group..."
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
if (_searchParams.has("group") && _searchParams.has("member")) {
|
||||
const groupId = _searchParams.get("group")
|
||||
const memberId = _searchParams.get("member")
|
||||
const providerName = _searchParams.get("provider")
|
||||
const clientRedirectUri =
|
||||
_searchParams.get("redirect_uri") || undefined
|
||||
|
||||
const state = await setOAuthState(
|
||||
groupId as string,
|
||||
memberId as string,
|
||||
providerName as string,
|
||||
clientRedirectUri
|
||||
)
|
||||
|
||||
if (state) {
|
||||
const clientId = import.meta.env[
|
||||
`VITE_${providerName?.toUpperCase()}_CLIENT_ID`
|
||||
]
|
||||
const redirectUri = import.meta.env[
|
||||
`VITE_${providerName?.toUpperCase()}_REDIRECT_URI`
|
||||
]
|
||||
|
||||
const provider = getProvider(providerName as string)
|
||||
|
||||
const authUrl = provider.getAuthUrl(
|
||||
clientId,
|
||||
state,
|
||||
redirectUri
|
||||
)
|
||||
|
||||
window.location.replace(authUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if (_searchParams.has("code") && _searchParams.has("state")) {
|
||||
const oAuthCode = _searchParams.get("code") as string
|
||||
const oAuthState = _searchParams.get("state") as string
|
||||
|
||||
const clientRedirectUri = await addMemberByReputation(
|
||||
oAuthState,
|
||||
oAuthCode
|
||||
)
|
||||
|
||||
if (clientRedirectUri) {
|
||||
window.location.replace(clientRedirectUri)
|
||||
} else {
|
||||
setMessage("You have joined the group!")
|
||||
}
|
||||
}
|
||||
})()
|
||||
}, [_searchParams])
|
||||
|
||||
return (
|
||||
<Flex flex="1" justify="center" align="center">
|
||||
<Text>{_message}</Text>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import GroupPage from "./pages/group"
|
||||
import GroupsPage from "./pages/groups"
|
||||
import HomePage from "./pages/home"
|
||||
import NewGroupPage from "./pages/new-group"
|
||||
import ReputationPage from "./pages/reputation"
|
||||
import CredentialsPage from "./pages/credentials"
|
||||
|
||||
export default function Routes(): JSX.Element {
|
||||
const { admin } = useContext(AuthContext)
|
||||
@@ -51,8 +51,8 @@ export default function Routes(): JSX.Element {
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "reputation",
|
||||
element: <ReputationPage />
|
||||
path: "credentials",
|
||||
element: <CredentialsPage />
|
||||
}
|
||||
]),
|
||||
[admin]
|
||||
|
||||
@@ -4,7 +4,7 @@ export type Group = {
|
||||
type?: string
|
||||
description?: string
|
||||
treeDepth: number
|
||||
reputationCriteria?: string
|
||||
credentials?: string
|
||||
fingerprintDuration?: number
|
||||
members: string[]
|
||||
admin: string
|
||||
|
||||
@@ -16,7 +16,7 @@ CREATE TABLE groups (
|
||||
admin_id character varying NOT NULL,
|
||||
tree_depth integer NOT NULL,
|
||||
fingerprint_duration integer NOT NULL,
|
||||
reputation_criteria text,
|
||||
credentials text,
|
||||
api_enabled boolean NOT NULL DEFAULT false,
|
||||
api_key character varying,
|
||||
created_at timestamp without time zone NOT NULL DEFAULT now(),
|
||||
@@ -34,7 +34,7 @@ CREATE TABLE members (
|
||||
|
||||
-- Table Definition ----------------------------------------------
|
||||
|
||||
CREATE TABLE reputation_accounts (
|
||||
CREATE TABLE oauth_accounts (
|
||||
"accountHash" character varying PRIMARY KEY,
|
||||
group_id character varying(32) REFERENCES groups(id),
|
||||
CONSTRAINT "UQ_f053a0e63cc508da7d0eccc677b" UNIQUE ("accountHash", group_id)
|
||||
@@ -53,4 +53,4 @@ CREATE TABLE invites (
|
||||
ALTER TABLE "groups" ADD FOREIGN KEY ("admin_id") REFERENCES "admins" ("id");
|
||||
ALTER TABLE "members" ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id");
|
||||
ALTER TABLE "invites" ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id");
|
||||
ALTER TABLE "reputation_accounts" ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id");
|
||||
ALTER TABLE "oauth_accounts" ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id");
|
||||
|
||||
@@ -33,7 +33,7 @@ describe("Bandada API SDK", () => {
|
||||
fingerprintDuration: 3600,
|
||||
createdAt: "2023-07-15T08:21:05.000Z",
|
||||
members: [],
|
||||
reputationCriteria: null
|
||||
credentials: null
|
||||
}
|
||||
])
|
||||
)
|
||||
@@ -54,7 +54,7 @@ describe("Bandada API SDK", () => {
|
||||
fingerprintDuration: 3600,
|
||||
createdAt: "2023-07-15T08:21:05.000Z",
|
||||
members: [],
|
||||
reputationCriteria: null
|
||||
credentials: null
|
||||
})
|
||||
)
|
||||
|
||||
@@ -184,7 +184,7 @@ describe("Bandada API SDK", () => {
|
||||
"0x63229164c457584616006e31d1e171e6cdd4163695bc9c4bf0227095998ffa4c",
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600,
|
||||
reputationCriteria: null,
|
||||
credentials: null,
|
||||
apiEnabled: false,
|
||||
apiKey: null,
|
||||
createdAt: "2023-08-09T18:09:53.000Z",
|
||||
|
||||
@@ -7,7 +7,7 @@ export type GroupResponse = {
|
||||
fingerprintDuration: number
|
||||
createdAt: Date
|
||||
members: string[]
|
||||
reputationCriteria: object
|
||||
credentials: object
|
||||
}
|
||||
|
||||
type Group = {
|
||||
@@ -17,7 +17,7 @@ type Group = {
|
||||
adminId: string
|
||||
treeDepth: number
|
||||
fingerprintDuration: number
|
||||
reputationCriteria: object
|
||||
credentials: object
|
||||
apiEnabled: boolean
|
||||
apiKey: string
|
||||
createdAt: Date
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Bandada reputation
|
||||
Bandada credentials
|
||||
</h1>
|
||||
<p align="center">Bandada library to validate users' reputation.</p>
|
||||
<p align="center">Bandada library to validate users' credentials.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -12,11 +12,11 @@
|
||||
<a href="https://github.com/privacy-scaling-explorations/bandada/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/privacy-scaling-explorations/bandada.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@bandada/reputation">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@bandada/reputation?style=flat-square" />
|
||||
<a href="https://www.npmjs.com/package/@bandada/credentials">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@bandada/credentials?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@bandada/reputation">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@bandada/reputation.svg?style=flat-square" />
|
||||
<a href="https://npmjs.org/package/@bandada/credentials">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@bandada/credentials.svg?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://eslint.org/">
|
||||
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
|
||||
@@ -46,33 +46,33 @@
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This package provides a function to validate users' reputation by using a set of extendable validators. |
|
||||
| This package provides a function to validate users' credentials by using a set of extendable validators. |
|
||||
| ------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@bandada/reputation` package with npm:
|
||||
Install the `@bandada/credentials` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @bandada/reputation
|
||||
npm i @bandada/credentials
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @bandada/reputation
|
||||
yarn add @bandada/credentials
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
\# **validateReputation**(reputationCriteria: _ReputationCriteria_, context: _Context_)
|
||||
\# **validateCredentials**(credentials: _Credentials_, context: _Context_)
|
||||
|
||||
```typescript
|
||||
import { validateReputation, githubFollowers } from "@bandada/reputation"
|
||||
import { validateCredentials, githubFollowers } from "@bandada/credentials"
|
||||
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubFollowers.id,
|
||||
criteria: {
|
||||
@@ -92,11 +92,11 @@ validateReputation(
|
||||
The library has been built to allow external devs to add their own validators. A validator is a simple file that exports 3 JavaScript values:
|
||||
|
||||
1. `id`: The validater id. It must be unique and capitalized (snake case).
|
||||
2. `criteriaABI`: The criteria ABI. It contains the structure of your reputation criteria with its types.
|
||||
3. `validate`: The validator handler. It usually consists of three steps: criteria types check, user data retrieval and reputation validation.
|
||||
2. `criteriaABI`: The criteria ABI. It contains the structure of your criteria with its types.
|
||||
3. `validate`: The validator handler. It usually consists of three steps: criteria types check, user data retrieval and credentials' validation.
|
||||
|
||||
```typescript
|
||||
import { Handler } from "@bandada/reputation"
|
||||
import { Handler } from "@bandada/credentials"
|
||||
|
||||
// Typescript type for the handler criteria.
|
||||
// This will be mainly used by this handler.
|
||||
@@ -108,7 +108,7 @@ const validator: Validator = {
|
||||
id: "GITHUB_FOLLOWERS",
|
||||
|
||||
// The criteria application binary interface. It contains
|
||||
// the structure of this validator reputation criteria
|
||||
// the structure of this validator credentials
|
||||
// with its parameter types.
|
||||
criteriaABI: {
|
||||
minFollowers: "number"
|
||||
@@ -116,15 +116,15 @@ const validator: Validator = {
|
||||
|
||||
/**
|
||||
* It checks if a user has more then 'minFollowers' followers.
|
||||
* @param criteria The reputation criteria used to check user's reputation.
|
||||
* @param criteria The criteria used to check user's credentials.
|
||||
* @param context Utility functions and other context variables.
|
||||
* @returns True if the user meets the reputation criteria.
|
||||
* @returns True if the user meets the credentials.
|
||||
*/
|
||||
async validate(criteria: Criteria, { utils }) {
|
||||
// Step 1: use the API to get the user's parameters.
|
||||
const { followers } = await utils.api("user")
|
||||
|
||||
// Step 2: check if they meet the validator reputation criteria.
|
||||
// Step 2: check if they meet the validator credentials.
|
||||
return followers >= criteria.minFollowers
|
||||
}
|
||||
}
|
||||
@@ -138,8 +138,8 @@ Testing your validator is also important. If you use Jest you can use some test
|
||||
import {
|
||||
addValidator,
|
||||
testUtils,
|
||||
validateReputation
|
||||
} from "@bandada/reputation"
|
||||
validateCredentials
|
||||
} from "@bandada/credentials"
|
||||
import githubFollowers from "./index"
|
||||
|
||||
describe("GithubFollowers", () => {
|
||||
@@ -152,7 +152,7 @@ describe("GithubFollowers", () => {
|
||||
followers: 110
|
||||
})
|
||||
|
||||
const result = await validateReputation(
|
||||
const result = await validateCredentials(
|
||||
{
|
||||
id: "GITHUB_FOLLOWERS",
|
||||
criteria: {
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@bandada/reputation",
|
||||
"name": "@bandada/credentials",
|
||||
"version": "0.12.0",
|
||||
"description": "Bandada library to validate users' reputation.",
|
||||
"description": "Bandada library to validate users' credentials.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
"exports": {
|
||||
@@ -16,7 +16,7 @@
|
||||
"README.md"
|
||||
],
|
||||
"repository": "https://github.com/privacy-scaling-explorations/bandada",
|
||||
"homepage": "https://github.com/privacy-scaling-explorations/bandada/tree/main/libs/reputation",
|
||||
"homepage": "https://github.com/privacy-scaling-explorations/bandada/tree/main/libs/credentials",
|
||||
"bugs": {
|
||||
"url": "https://github.com/privacy-scaling-explorations/bandada.git/issues"
|
||||
},
|
||||
@@ -2,7 +2,7 @@ import { Validator } from "./types"
|
||||
import validators from "./validators"
|
||||
|
||||
/**
|
||||
* It adds a new reputation validator.
|
||||
* It adds a new credential's validator.
|
||||
* @param validator The validator to be added.
|
||||
*/
|
||||
export default function addValidator(validator: Validator) {
|
||||
@@ -2,7 +2,7 @@ import addValidator from "./addValidator"
|
||||
import { Validator } from "./types"
|
||||
|
||||
/**
|
||||
* It adds a list of new reputation validators.
|
||||
* It adds a list of new credential's validators.
|
||||
* @param validators The list of validators to be added.
|
||||
*/
|
||||
export default function addValidators(validators: Validator[]) {
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* It checks if the criteria parameters are the right ones. Criteria must
|
||||
* contain the right parameters and the right types for each of them.
|
||||
* @param criteria The reputation criteria to check.
|
||||
* @param criteria The credentials to check.
|
||||
* @param criteriaABI The criteria ABI.
|
||||
*/
|
||||
export default function checkCriteria(criteria: any, criteriaABI: any) {
|
||||
@@ -7,7 +7,7 @@ import getValidator from "./getValidator"
|
||||
import providers from "./providers"
|
||||
import validators from "./validators"
|
||||
|
||||
describe("Reputation library", () => {
|
||||
describe("Credentials library", () => {
|
||||
describe("# addProvider", () => {
|
||||
it("Should add a provider to the list of supported providers", () => {
|
||||
addProvider({} as any)
|
||||
@@ -6,14 +6,14 @@ import getProvider from "./getProvider"
|
||||
import getValidator from "./getValidator"
|
||||
import providers from "./providers"
|
||||
import * as testUtils from "./testUtils"
|
||||
import validateReputation from "./validateReputation"
|
||||
import validateCredentials from "./validateCredentials"
|
||||
import validators from "./validators"
|
||||
|
||||
export * from "./providers/index"
|
||||
export * from "./types"
|
||||
export * from "./validators/index"
|
||||
export {
|
||||
validateReputation,
|
||||
validateCredentials,
|
||||
addValidator,
|
||||
getProvider,
|
||||
getValidator,
|
||||
@@ -30,7 +30,7 @@ export interface Validator {
|
||||
validate: Handler
|
||||
}
|
||||
|
||||
export type ReputationCriteria = {
|
||||
export type Credentials = {
|
||||
id: string
|
||||
criteria: any
|
||||
}
|
||||
@@ -2,18 +2,18 @@ import checkCriteria from "./checkCriteria"
|
||||
import getAPI from "./getAPI"
|
||||
import getProvider from "./getProvider"
|
||||
import getValidator from "./getValidator"
|
||||
import { Context, ReputationCriteria } from "./types"
|
||||
import { Context, Credentials } from "./types"
|
||||
|
||||
/**
|
||||
* It checks if the user meets the reputation criteria of a group.
|
||||
* It also adds utility functions to the reputation context that
|
||||
* It checks if the user meets the credentials of a group.
|
||||
* It also adds utility functions to the credentials context that
|
||||
* can be used by validators.
|
||||
* @param reputationCriteria The reputation criteria of a group.
|
||||
* @param credentials The credentials of a group.
|
||||
* @param context A set of context variables.
|
||||
* @returns True if the user meets the reputation criteria.
|
||||
* @returns True if the user meets the credentials.
|
||||
*/
|
||||
export default async function validateReputation(
|
||||
{ id, criteria }: ReputationCriteria,
|
||||
export default async function validateCredentials(
|
||||
{ id, criteria }: Credentials,
|
||||
context: Omit<Context, "utils">
|
||||
): Promise<boolean> {
|
||||
const validator = getValidator(id)
|
||||
@@ -1,9 +1,9 @@
|
||||
import { validateReputation } from "../.."
|
||||
import { validateCredentials } from "../.."
|
||||
import githubFollowers from "./index"
|
||||
|
||||
describe("GithubFollowers", () => {
|
||||
it("Should return true if a Github user has more than 100 followers", async () => {
|
||||
const result = await validateReputation(
|
||||
const result = await validateCredentials(
|
||||
{
|
||||
id: githubFollowers.id,
|
||||
criteria: {
|
||||
@@ -23,7 +23,7 @@ describe("GithubFollowers", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter is missing", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubFollowers.id,
|
||||
criteria: {}
|
||||
@@ -43,7 +43,7 @@ describe("GithubFollowers", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter should not exist", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubFollowers.id,
|
||||
criteria: {
|
||||
@@ -66,7 +66,7 @@ describe("GithubFollowers", () => {
|
||||
|
||||
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubFollowers.id,
|
||||
criteria: {
|
||||
@@ -13,9 +13,9 @@ const validator: Validator = {
|
||||
|
||||
/**
|
||||
* It checks if a user has more then 'minFollowers' followers.
|
||||
* @param criteria The reputation criteria used to check user's reputation.
|
||||
* @param criteria The criteria used to check user's credentials.
|
||||
* @param context Utility functions and other context variables.
|
||||
* @returns True if the user meets the reputation criteria.
|
||||
* @returns True if the user meets the criteria.
|
||||
*/
|
||||
async validate(criteria: Criteria, { profile }) {
|
||||
return profile.followers >= criteria.minFollowers
|
||||
@@ -1,4 +1,4 @@
|
||||
import { testUtils, validateReputation } from "../.."
|
||||
import { testUtils, validateCredentials } from "../.."
|
||||
import githubPersonalStars from "./index"
|
||||
|
||||
describe("GithubPersonalStars", () => {
|
||||
@@ -10,7 +10,7 @@ describe("GithubPersonalStars", () => {
|
||||
Array.from(Array(20).keys()).map(() => ({ stargazers_count: 10 }))
|
||||
)
|
||||
|
||||
const result = await validateReputation(
|
||||
const result = await validateCredentials(
|
||||
{
|
||||
id: githubPersonalStars.id,
|
||||
criteria: {
|
||||
@@ -28,7 +28,7 @@ describe("GithubPersonalStars", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter is missing", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubPersonalStars.id,
|
||||
criteria: {}
|
||||
@@ -46,7 +46,7 @@ describe("GithubPersonalStars", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter should not exist", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubPersonalStars.id,
|
||||
criteria: {
|
||||
@@ -67,7 +67,7 @@ describe("GithubPersonalStars", () => {
|
||||
|
||||
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubPersonalStars.id,
|
||||
criteria: {
|
||||
@@ -13,9 +13,9 @@ const validator: Validator = {
|
||||
|
||||
/**
|
||||
* It checks if a user has more then 'minStars' stars in their personal repository.
|
||||
* @param criteria The reputation criteria used to check user's reputation.
|
||||
* @param criteria The criteria used to check user's credentials.
|
||||
* @param context Utility functions and other context variables.
|
||||
* @returns True if the user meets the reputation criteria.
|
||||
* @returns True if the user meets the criteria.
|
||||
*/
|
||||
async validate(criteria: Criteria, { utils }) {
|
||||
let allRepositories = []
|
||||
@@ -1,4 +1,4 @@
|
||||
import { testUtils, validateReputation } from "../.."
|
||||
import { testUtils, validateCredentials } from "../.."
|
||||
import githubRepositoryCommits from "./index"
|
||||
|
||||
describe("GithubRepositoryCommits", () => {
|
||||
@@ -6,7 +6,7 @@ describe("GithubRepositoryCommits", () => {
|
||||
testUtils.mockAPIOnce(Array.from(Array(100).keys()))
|
||||
testUtils.mockAPIOnce(Array.from(Array(80).keys()))
|
||||
|
||||
const result = await validateReputation(
|
||||
const result = await validateCredentials(
|
||||
{
|
||||
id: githubRepositoryCommits.id,
|
||||
criteria: {
|
||||
@@ -25,7 +25,7 @@ describe("GithubRepositoryCommits", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter is missing", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubRepositoryCommits.id,
|
||||
criteria: { repository: "hello-worId" }
|
||||
@@ -43,7 +43,7 @@ describe("GithubRepositoryCommits", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter should not exist", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubRepositoryCommits.id,
|
||||
criteria: {
|
||||
@@ -65,7 +65,7 @@ describe("GithubRepositoryCommits", () => {
|
||||
|
||||
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: githubRepositoryCommits.id,
|
||||
criteria: {
|
||||
@@ -15,9 +15,9 @@ const validator: Validator = {
|
||||
|
||||
/**
|
||||
* It checks if a user has more then 'minCommits' commits in a specific repo.
|
||||
* @param criteria The reputation criteria used to check user's reputation.
|
||||
* @param criteria The criteria used to check user's credentials.
|
||||
* @param context Utility functions and other context variables.
|
||||
* @returns True if the user meets the reputation criteria.
|
||||
* @returns True if the user meets the criteria.
|
||||
*/
|
||||
async validate(criteria: Criteria, { utils, profile }) {
|
||||
let allCommits = []
|
||||
@@ -1,4 +1,4 @@
|
||||
import { testUtils, validateReputation } from "../.."
|
||||
import { testUtils, validateCredentials } from "../.."
|
||||
import twitterFollowers from "./index"
|
||||
|
||||
describe("TwitterFollowers", () => {
|
||||
@@ -11,7 +11,7 @@ describe("TwitterFollowers", () => {
|
||||
}
|
||||
})
|
||||
|
||||
const result = await validateReputation(
|
||||
const result = await validateCredentials(
|
||||
{
|
||||
id: twitterFollowers.id,
|
||||
criteria: {
|
||||
@@ -29,7 +29,7 @@ describe("TwitterFollowers", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter is missing", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: twitterFollowers.id,
|
||||
criteria: {}
|
||||
@@ -47,7 +47,7 @@ describe("TwitterFollowers", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter should not exist", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: twitterFollowers.id,
|
||||
criteria: {
|
||||
@@ -68,7 +68,7 @@ describe("TwitterFollowers", () => {
|
||||
|
||||
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: twitterFollowers.id,
|
||||
criteria: {
|
||||
@@ -13,9 +13,9 @@ const validator: Validator = {
|
||||
|
||||
/**
|
||||
* It checks if a user has more then 'minFollowers' followers.
|
||||
* @param criteria The reputation criteria used to check user's reputation.
|
||||
* @param criteria The criteria used to check user's credentials.
|
||||
* @param context Utility functions and other context variables.
|
||||
* @returns True if the user meets the reputation criteria.
|
||||
* @returns True if the user meets the criteria.
|
||||
*/
|
||||
async validate(criteria: Criteria, { utils }) {
|
||||
const { data } = await utils.api("users/me?user.fields=public_metrics")
|
||||
@@ -1,4 +1,4 @@
|
||||
import { testUtils, validateReputation } from "../.."
|
||||
import { testUtils, validateCredentials } from "../.."
|
||||
import twitterFollowingUser from "./index"
|
||||
|
||||
describe("TwitterFollowingUser", () => {
|
||||
@@ -14,7 +14,7 @@ describe("TwitterFollowingUser", () => {
|
||||
]
|
||||
})
|
||||
|
||||
const result = await validateReputation(
|
||||
const result = await validateCredentials(
|
||||
{
|
||||
id: twitterFollowingUser.id,
|
||||
criteria: {
|
||||
@@ -32,7 +32,7 @@ describe("TwitterFollowingUser", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter is missing", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: twitterFollowingUser.id,
|
||||
criteria: {}
|
||||
@@ -50,7 +50,7 @@ describe("TwitterFollowingUser", () => {
|
||||
|
||||
it("Should throw an error if a criteria parameter should not exist", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: twitterFollowingUser.id,
|
||||
criteria: {
|
||||
@@ -71,7 +71,7 @@ describe("TwitterFollowingUser", () => {
|
||||
|
||||
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
|
||||
const fun = () =>
|
||||
validateReputation(
|
||||
validateCredentials(
|
||||
{
|
||||
id: twitterFollowingUser.id,
|
||||
criteria: {
|
||||
@@ -13,9 +13,9 @@ const validator: Validator = {
|
||||
|
||||
/**
|
||||
* It checks if a Twitter user follows a specific page.
|
||||
* @param criteria The reputation criteria used to check user's reputation.
|
||||
* @param criteria The criteria used to check user's credentials.
|
||||
* @param context Utility functions and other context variables.
|
||||
* @returns True if the user meets the reputation criteria.
|
||||
* @returns True if the user meets the criteria.
|
||||
*/
|
||||
async validate(criteria: Criteria, { utils, profile }) {
|
||||
let allFollowing = []
|
||||
30
yarn.lock
30
yarn.lock
@@ -805,6 +805,19 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@bandada/credentials@0.12.0, @bandada/credentials@workspace:libs/credentials":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@bandada/credentials@workspace:libs/credentials"
|
||||
dependencies:
|
||||
"@bandada/utils": 0.12.0
|
||||
"@rollup/plugin-typescript": ^11.0.0
|
||||
rimraf: ^4.1.2
|
||||
rollup: ^3.17.2
|
||||
rollup-plugin-cleanup: ^3.2.1
|
||||
typescript: ^4.9.5
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@bandada/hardhat@workspace:libs/hardhat":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@bandada/hardhat@workspace:libs/hardhat"
|
||||
@@ -827,19 +840,6 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@bandada/reputation@0.12.0, @bandada/reputation@workspace:libs/reputation":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@bandada/reputation@workspace:libs/reputation"
|
||||
dependencies:
|
||||
"@bandada/utils": 0.12.0
|
||||
"@rollup/plugin-typescript": ^11.0.0
|
||||
rimraf: ^4.1.2
|
||||
rollup: ^3.17.2
|
||||
rollup-plugin-cleanup: ^3.2.1
|
||||
typescript: ^4.9.5
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@bandada/utils@0.12.0, @bandada/utils@workspace:libs/utils":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@bandada/utils@workspace:libs/utils"
|
||||
@@ -7610,7 +7610,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "api@workspace:apps/api"
|
||||
dependencies:
|
||||
"@bandada/reputation": 0.12.0
|
||||
"@bandada/credentials": 0.12.0
|
||||
"@bandada/utils": 0.12.0
|
||||
"@ethersproject/hash": ^5.7.0
|
||||
"@nestjs/cli": ^9.0.0
|
||||
@@ -9864,7 +9864,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "dashboard@workspace:apps/dashboard"
|
||||
dependencies:
|
||||
"@bandada/reputation": 0.12.0
|
||||
"@bandada/credentials": 0.12.0
|
||||
"@bandada/utils": 0.12.0
|
||||
"@chakra-ui/react": ^2.5.1
|
||||
"@chakra-ui/theme-tools": ^2.0.16
|
||||
|
||||
Reference in New Issue
Block a user