Skip to content

Commit 6b9d95e

Browse files
committed
initial commit
1 parent 26ab1ab commit 6b9d95e

8 files changed

+5894
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,6 @@ dist
128128
.yarn/build-state.yml
129129
.yarn/install-state.gz
130130
.pnp.*
131+
132+
133+
temp/

index.js

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'dotenv/config'
2+
import fs from 'fs';
3+
import { Wallet } from '@ethersproject/wallet'
4+
import IndexClient from "@indexnetwork/sdk";
5+
import { getAllTeams } from './utils/getAllTeams.js';
6+
import { createTeam } from './utils/getComposeClient.js';
7+
8+
// Function to load teams from cache or fetch new ones
9+
const loadTeams = async () => {
10+
const fromCache = true; // Toggle this to switch between cache and fetching new data, to not hurt PL directory server.
11+
if (fromCache) {
12+
return JSON.parse(fs.readFileSync("temp/teams.json"));
13+
} else {
14+
const teams = await getAllTeams();
15+
fs.writeFileSync("temp/teams.json", JSON.stringify(teams));
16+
return teams;
17+
}
18+
};
19+
20+
21+
const go = async () => {
22+
try {
23+
24+
const wallet = Wallet.createRandom(process.env.WALLET_PRIVATE_KEY)
25+
26+
const teams = await loadTeams(); // Load teams from cache or fetch new ones
27+
28+
let createdTeams = [];
29+
for (const t of teams) {
30+
let createdTeam = await createTeam(wallet, t);
31+
createdTeams.push(createdTeam)
32+
}
33+
34+
const indexClient = new IndexClient({
35+
privateKey: wallet.privateKey,
36+
domain: "index.network",
37+
network: "ethereum",
38+
});
39+
40+
await indexClient.authenticate();
41+
42+
const indexId = await indexClient.createIndex({
43+
title: "PL Ecosystem Teams",
44+
});
45+
46+
for (const t of createdTeams) {
47+
await indexClient.addIndexItem(indexId, t)
48+
}
49+
50+
const queryResponse = await indexClient.query({
51+
query: "Which team is building a decentralized semantic index?",
52+
indexes: [indexId],
53+
});
54+
55+
} catch (error) {
56+
console.error('Exception:', error);
57+
}
58+
};
59+
60+
go()

package.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "Indexnetwork-pl-directory",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {
6+
"import": "node index.js"
7+
},
8+
"type": "module",
9+
"license": "MIT",
10+
"dependencies": {
11+
"@composedb/client": "^0.7.1",
12+
"@didtools/cacao": "^3.0.1",
13+
"@ethersproject/address": "^5.7.0",
14+
"@ethersproject/wallet": "^5.7.0",
15+
"@indexnetwork/sdk": "^0.0.3",
16+
"@stablelib/random": "^1.0.2",
17+
"did-session": "^3.1.0",
18+
"dotenv": "^16.4.5",
19+
"ethers": "^6.11.1",
20+
"yarn": "^1.22.22"
21+
}
22+
}

schema.graphql

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
type Technology {
2+
uid: String! @string(maxLength: 100)
3+
title: String! @string(maxLength: 100)
4+
createdAt: DateTime!
5+
updatedAt: DateTime!
6+
}
7+
8+
type IndustryTag {
9+
uid: String! @string(maxLength: 100)
10+
title: String! @string(maxLength: 100)
11+
definition: String @string(maxLength: 1000)
12+
airtableRecId: String @string(maxLength: 100)
13+
createdAt: DateTime!
14+
updatedAt: DateTime!
15+
industryCategoryUid: String @string(maxLength: 100)
16+
}
17+
18+
type MembershipSource {
19+
uid: String! @string(maxLength: 100)
20+
title: String! @string(maxLength: 100)
21+
createdAt: DateTime!
22+
updatedAt: DateTime!
23+
}
24+
25+
type Skill {
26+
title: String! @string(maxLength: 50)
27+
}
28+
29+
30+
type ProjectContribution {
31+
uid: String! @string(maxLength: 100)
32+
role: String! @string(maxLength: 100)
33+
description: String @string(maxLength: 5000)
34+
currentProject: Boolean!
35+
startDate: DateTime
36+
endDate: DateTime
37+
memberUid: String! @string(maxLength: 100)
38+
projectUid: String! @string(maxLength: 100)
39+
}
40+
41+
42+
type TeamRole {
43+
uid: String! @string(maxLength: 100)
44+
name: String! @string(maxLength: 100)
45+
role: String @string(maxLength: 100)
46+
teamLead: Boolean!
47+
mainTeam: Boolean!
48+
}
49+
50+
51+
type Member {
52+
memberId: String! @string(maxLength: 100)
53+
name: String! @string(maxLength: 100)
54+
image: String @string(maxLength: 300)
55+
githubHandle: String @string(maxLength: 100)
56+
discordHandle: String @string(maxLength: 100)
57+
telegramHandle: String @string(maxLength: 100)
58+
twitter: String @string(maxLength: 100)
59+
officeHours: String @string(maxLength: 100)
60+
location: String @string(maxLength: 100)
61+
skills: [Skill!] @list(maxLength: 30)
62+
teamLead: Boolean!
63+
projectContributions: [ProjectContribution!]! @list(maxLength: 30)
64+
teams: [TeamRole!] @list(maxLength: 30)
65+
mainTeam: TeamRole
66+
openToWork: Boolean!
67+
linkedinHandle: String @string(maxLength: 300)
68+
repositories: [String!] @string(maxLength: 100) @list(maxLength: 30)
69+
preferences: String @string(maxLength: 500)
70+
}
71+
72+
73+
type Team @createModel(accountRelation: LIST, description: "Represents teams within the Protocol Labs ecosystem"){
74+
teamId: String! @string(maxLength: 100)
75+
name: String! @string(maxLength: 100)
76+
logo: String @string(maxLength: 300)
77+
website: String @string(maxLength: 300)
78+
twitter: String @string(maxLength: 100)
79+
shortDescription: String @string(maxLength: 5000)
80+
longDescription: String @string(maxLength: 10000)
81+
technologies: [Technology!] @list(maxLength: 30)
82+
fundingStage: String @string(maxLength: 50)
83+
industryTags: [IndustryTag!] @list(maxLength: 30)
84+
membershipSources: [MembershipSource!] @list(maxLength: 30)
85+
members: [Member!] @list(maxLength: 1000)
86+
contactMethod: String @string(maxLength: 300)
87+
linkedinHandle: String @string(maxLength: 300)
88+
}

utils/getAllTeams.js

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
2+
const transformDataToTeamObject = (data) => {
3+
const { team, members } = data.pageProps;
4+
5+
// Transform technologies, industryTags, and membershipSources
6+
const technologies = team.technologies.map(tech => ({
7+
uid: tech.uid,
8+
title: tech.title,
9+
createdAt: tech.createdAt,
10+
updatedAt: tech.updatedAt,
11+
}));
12+
13+
const industryTags = team.industryTags.map(tag => ({
14+
uid: tag.uid,
15+
title: tag.title,
16+
definition: tag.definition || '',
17+
airtableRecId: tag.airtableRecId || '',
18+
createdAt: tag.createdAt,
19+
updatedAt: tag.updatedAt,
20+
industryCategoryUid: tag.industryCategoryUid,
21+
}));
22+
23+
const membershipSources = team.membershipSources.map(source => ({
24+
uid: source.uid,
25+
title: source.title,
26+
createdAt: source.createdAt,
27+
updatedAt: source.updatedAt,
28+
}));
29+
30+
// Transform members
31+
const transformedMembers = members.map(member => ({
32+
memberId: member.id,
33+
name: member.name,
34+
image: member.image,
35+
githubHandle: member.githubHandle || '',
36+
discordHandle: member.discordHandle || '',
37+
telegramHandle: member.telegramHandle || '',
38+
twitter: member.twitter || '',
39+
officeHours: member.officeHours || '',
40+
location: member.location,
41+
skills: member.skills.map(skill => ({ title: skill.title })),
42+
teamLead: member.teamLead,
43+
projectContributions: member.projectContributions.map(pc => ({
44+
uid: pc.uid || '',
45+
role: pc.role || '',
46+
description: pc.description || '',
47+
currentProject: pc.currentProject || false,
48+
startDate: pc.startDate || '',
49+
endDate: pc.endDate || null,
50+
memberUid: pc.memberUid || '',
51+
projectUid: pc.projectUid || ''
52+
})),
53+
teams: member.teams.map(team => ({
54+
uid: team.id,
55+
name: team.name,
56+
role: team.role,
57+
teamLead: team.teamLead,
58+
mainTeam: team.mainTeam,
59+
})),
60+
mainTeam: member.mainTeam ? {
61+
uid: member.mainTeam.id,
62+
name: member.mainTeam.name,
63+
role: member.mainTeam.role,
64+
teamLead: member.mainTeam.teamLead,
65+
mainTeam: member.mainTeam.mainTeam,
66+
} : null,
67+
openToWork: member.openToWork,
68+
linkedinHandle: member.linkedinHandle || null,
69+
repositories: [], // Assuming structure; needs details for proper mapping
70+
preferences: '', // Assuming structure; needs details for proper mapping
71+
}));
72+
73+
return {
74+
teamId: team.id,
75+
name: team.name,
76+
logo: team.logo,
77+
website: team.website,
78+
twitter: team.twitter,
79+
shortDescription: team.shortDescription,
80+
longDescription: team.longDescription,
81+
technologies,
82+
fundingStage: team.fundingStage,
83+
industryTags,
84+
membershipSources,
85+
members: transformedMembers,
86+
contactMethod: team.contactMethod || null,
87+
linkedinHandle: team.linkedinHandle || null,
88+
};
89+
}
90+
91+
export const getTeam = async (teamId) => {
92+
const res = await fetch(`https://directory.plnetwork.io/_next/data/yoMEE54BV2SkIRBb76jyU/teams/${teamId}.json?backLink=%2Fteams`)
93+
const teamData = await res.json();
94+
const teamObject = transformDataToTeamObject(teamData);
95+
return teamObject;
96+
}
97+
export const getAllTeams = async () => {
98+
const res = await fetch('https://directory.plnetwork.io/_next/data/yoMEE54BV2SkIRBb76jyU/teams.json')
99+
const allTeams = await res.json();
100+
const teamList = allTeams.pageProps.teams.map(t => t.id);
101+
102+
const body = await Promise.all(teamList.map(t => getTeam(t)))
103+
return body
104+
105+
}

utils/getComposeClient.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { ComposeClient } from '@composedb/client';
2+
import { definition } from '../temp/merged-runtime.js';
3+
import { getSession } from './getSession.js';
4+
5+
const compose = new ComposeClient({ ceramic: process.env.CERAMIC_URL, definition });
6+
7+
// Function to create a team
8+
export const createTeam = async (wallet, team) => {
9+
try {
10+
if(!compose.did || !compose.did.authenticated){
11+
const session = await getSession(wallet);
12+
compose.setDID(session.did);
13+
}
14+
const { data, errors } = await compose.executeQuery(`
15+
mutation CreateTeam($input: CreateTeamInput!) {
16+
createTeam(input: $input) {
17+
document {
18+
id
19+
}
20+
}
21+
}`, { input: { content: team } });
22+
23+
if(data && data.createTeam && data.createTeam.document){
24+
return data.createTeam.document.id;
25+
}
26+
if (errors) {
27+
console.error('Error creating team:', errors[0]);
28+
return null; // Return null or appropriate value in case of error
29+
}
30+
} catch (error) {
31+
console.error('Exception in createTeam:', error);
32+
return null; // Ensure function returns even on exception
33+
}
34+
};

utils/getSession.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
import { Cacao, SiweMessage } from '@didtools/cacao'
3+
import { randomString, randomBytes } from '@stablelib/random';
4+
import { DIDSession, createDIDKey, createDIDCacao } from 'did-session';
5+
6+
7+
export const getSession = async (wallet) => {
8+
9+
const address = wallet.address
10+
11+
// DID Key Generation: Develop a DID key using a random seed
12+
const keySeed = randomBytes(32);
13+
const didKey = await createDIDKey(keySeed);
14+
15+
const now = new Date();
16+
const threeMonthsLater = new Date(now.getTime() + 90 * 24 * 60 * 60 * 1000);
17+
18+
// Create a SIWE message for authentication.
19+
const siweMessage = new SiweMessage({
20+
domain: "index.network",
21+
address: address,
22+
statement: "Give this application access to some of your data on Ceramic",
23+
uri: didKey.id,
24+
version: "1",
25+
chainId: "1",
26+
nonce: randomString(10),
27+
issuedAt: now.toISOString(),
28+
expirationTime: threeMonthsLater.toISOString(),
29+
resources: ["ceramic://*"],
30+
})
31+
32+
// Sign the SIWE message with the wallet's private key.
33+
const signature = await wallet.signMessage(siweMessage.toMessage())
34+
35+
siweMessage.signature = signature
36+
37+
// Create a new session using the CACAO, key seed, and DID.
38+
const cacao = Cacao.fromSiweMessage(siweMessage);
39+
const did = await createDIDCacao(didKey, cacao);
40+
const newSession = new DIDSession({ cacao, keySeed, did });
41+
42+
// Here is our authorization token.
43+
return newSession
44+
45+
}

0 commit comments

Comments
 (0)