Skip to content

Commit 7b98f17

Browse files
authored
Merge pull request #3 from ceptor-club/personal/allan/onboarding
onboarding workflow - user register API
2 parents 54c016b + 24e3d51 commit 7b98f17

File tree

9 files changed

+1477
-199
lines changed

9 files changed

+1477
-199
lines changed

packages/nextjs/.env.example

+5-4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
# If not set, we provide default values (check `scaffold.config.ts`) so developers can start prototyping out of the box,
77
# but we recommend getting your own API Keys for Production Apps.
8-
9-
# To access the values stored in this env file you can use: process.env.VARIABLENAME
10-
# You'll need to prefix the variables names with NEXT_PUBLIC_ if you want to access them on the client side.
11-
# More info: https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables
128
NEXT_PUBLIC_ALCHEMY_API_KEY=
139
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=
10+
11+
# This is required, no default values, if you don't known, you can ask in the discord channel
12+
MONGO_CONN_STR=
13+
MONGO_DB_NAME=
14+

packages/nextjs/README.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# How to call the restful API
2+
3+
## Register user API
4+
5+
Below is an example of how to call user API
6+
7+
```
8+
POST http://localhost:3000/api/user
9+
Content-Type: application/json
10+
11+
{
12+
"id": "did:ethr:0xaa36a7:0xAD849E351533Db54D0BD22De977a653edfd9EEb8",
13+
"name": "user",
14+
"email": "[email protected]",
15+
"role": "other",
16+
"projects": ["tech", "game" ],
17+
"initiative": 8,
18+
"contactMethod": "email",
19+
"indiscord": false
20+
}
21+
22+
HTTP/1.1 200 OK
23+
Content-Type: application/json; charset=utf-8
24+
ETag: "k5xu2dpe1l2b"
25+
Content-Length: 83
26+
Vary: Accept-Encoding
27+
Date: Wed, 22 May 2024 12:10:24 GMT
28+
Connection: keep-alive
29+
Keep-Alive: timeout=5
30+
31+
{"message":"user 0xaa36a7:0xAD849E351533Db54D0BD22De977a653edfd9EEb8 user joined."}
32+
```
33+
34+
### how to specify the user id
35+
36+
the id is the did format, for ethrethereum address, its format is: did:ethr:<chainid>:<ethr address>
37+
the chainid should be hex format, you can find the chainid for each network in the following link, you can omit the chainid for mainnet.
38+
https://github.com/uport-project/ethr-did-registry?tab=readme-ov-file#contract-deployments
39+
40+
the ethr address should be hex format.

packages/nextjs/app/api/user/route.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import getConfig from "next/config";
2+
import { DuplicateUserError } from "../../../models/errors";
3+
import { User } from "../../../models/user";
4+
import { saveUser } from "../../../services/mongo/mongo";
5+
import { Resolver, parse } from "did-resolver";
6+
import ethr from "ethr-did-resolver";
7+
8+
export async function POST(request: Request) {
9+
const { serverRuntimeConfig } = getConfig();
10+
const ethrResolver = ethr.getResolver(serverRuntimeConfig.providerConfig);
11+
const resolver = new Resolver(ethrResolver);
12+
13+
const user: User = await request.json();
14+
const did = parse(user.id);
15+
const doc = await resolver.resolve(user.id);
16+
if (doc.didResolutionMetadata.error || did === null) {
17+
return new Response(`invalid user id: ${user.id}.`, { status: 400 });
18+
}
19+
20+
user.address = did.id;
21+
try {
22+
await saveUser(user);
23+
return new Response(`user ${user.address} ${user.name} joined.`, { status: 400 });
24+
} catch (err) {
25+
if (err instanceof DuplicateUserError) {
26+
return new Response(`user ${user.id} already exists.`, { status: 400 });
27+
} else {
28+
return new Response(`internal server error: ${err}.`, { status: 500 });
29+
}
30+
}
31+
}

packages/nextjs/models/errors.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export class DuplicateUserError extends Error {}

packages/nextjs/models/user.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export interface User {
2+
id: string;
3+
name: string;
4+
email: string;
5+
address: string;
6+
role: string;
7+
projects: string[];
8+
initiative: number;
9+
contactMethod: string;
10+
indiscord: boolean;
11+
background: string;
12+
availableTime: string;
13+
links: string[];
14+
attraction: string;
15+
}

packages/nextjs/next.config.js

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// @ts-check
2+
const { MONGO_CONN_STR, MONGO_DB_NAME } = process.env;
23

34
/** @type {import('next').NextConfig} */
45
const nextConfig = {
@@ -9,6 +10,18 @@ const nextConfig = {
910
eslint: {
1011
ignoreDuringBuilds: process.env.NEXT_PUBLIC_IGNORE_BUILD_ERROR === "true",
1112
},
13+
serverRuntimeConfig: {
14+
providerConfig: {
15+
networks: [
16+
{ name: "mainnet", rpcUrl: "https://eth.drpc.org" },
17+
{ name: "sepolia", rpcUrl: "https://sepolia.drpc.org", registry: "0x03d5003bf0e79c5f5223588f347eba39afbc3818" },
18+
],
19+
},
20+
mongoConfig: {
21+
connectionStr: MONGO_CONN_STR,
22+
db: MONGO_DB_NAME,
23+
},
24+
},
1225
webpack: config => {
1326
config.resolve.fallback = { fs: false, net: false, tls: false };
1427
config.externals.push("pino-pretty", "lokijs", "encoding");

packages/nextjs/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
"@uniswap/v2-sdk": "^3.0.1",
2222
"blo": "^1.0.1",
2323
"daisyui": "4.5.0",
24+
"did-jwt": "^8.0.4",
25+
"did-jwt-vc": "^4.0.4",
26+
"ethr-did": "^3.0.21",
27+
"ethr-did-resolver": "^10.1.5",
28+
"mongodb": "^4.14.0",
2429
"next": "^14.0.4",
2530
"next-themes": "^0.2.1",
2631
"nprogress": "^0.2.0",
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import getConfig from "next/config";
2+
import { DuplicateUserError } from "../../models/errors";
3+
import { User } from "../../models/user";
4+
import { InsertOneResult, MongoClient, MongoServerError, WithId } from "mongodb";
5+
6+
const { serverRuntimeConfig } = getConfig();
7+
8+
if (!serverRuntimeConfig.mongoConfig.connectionStr) {
9+
throw new Error("mongo connection string is missing, please check MONGO_CONN_STR in your env file");
10+
}
11+
12+
if (!serverRuntimeConfig.mongoConfig.db) {
13+
throw new Error("mongo db name is missing, please check MONGO_DB_NAME in your env file");
14+
}
15+
16+
const client = new MongoClient(serverRuntimeConfig.mongoConfig.connectionStr);
17+
const usersCollection = client.db(serverRuntimeConfig.mongoConfig.db).collection<User>("User");
18+
19+
//function to connect to mongoDB and save a user to the database
20+
export async function saveUser(user: User): Promise<InsertOneResult<User>> {
21+
try {
22+
return await usersCollection.insertOne(user);
23+
} catch (err) {
24+
if (err instanceof MongoServerError && err.code === 11000) {
25+
throw new DuplicateUserError();
26+
}
27+
throw err;
28+
}
29+
}
30+
31+
//function to connect to mongoDB and get a user from the database
32+
export async function getUserByAddress(address: string): Promise<WithId<User> | null> {
33+
return await usersCollection.findOne({ address: address });
34+
}
35+
36+
//function to get user by _id
37+
export async function getUserById(id: string): Promise<WithId<User> | null> {
38+
return await usersCollection.findOne({ id: id });
39+
}
40+
41+
//function to list all users
42+
export async function getAllUsers(): Promise<WithId<User>[]> {
43+
const usersCursor = usersCollection.find();
44+
return await usersCursor.toArray();
45+
}

0 commit comments

Comments
 (0)