Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(location): adding implem #18

Merged
merged 2 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*

.env
.env
/read
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@testing-library/user-event": "^13.5.0",
"@types/file-saver": "^2.0.7",
"@types/react-modal": "^3.16.3",
"@upstash/redis": "^1.34.0",
"crypto-browserify": "^3.12.0",
"ethers": "^6.13.1",
"ethers-multicall-provider": "^6.4.0",
Expand Down
13 changes: 12 additions & 1 deletion src/component/OracleDecoder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import CheckItemFeeds from "./common/CheckItemFeeds";
import useRouteMatch from "../hooks/testor/useRouteMatch";
import CheckItemPrice from "./common/CheckItemPrice";
import useOraclePriceCheck from "../hooks/testor/useOraclePriceCheck";
import { initializeUser, recordQuery } from "../services/rate/userClick";

const ethLogo = "https://cdn.morpho.org/assets/chains/eth.svg";
const baseLogo = "https://cdn.morpho.org/assets/chains/base.png";
Expand All @@ -34,6 +35,7 @@ const OracleDecoder = () => {
const [countdown, setCountdown] = useState(5);
const [submitStarted, setSubmitStarted] = useState(false);
const [triggerCheck, setTriggerCheck] = useState(false);
const [userId, setUserId] = useState<string | null>(null);

const [selectedNetwork, setSelectedNetwork] = useState<{
value: number;
Expand Down Expand Up @@ -147,7 +149,16 @@ const OracleDecoder = () => {
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
resetState();

try {
const locationAddress = window.location.hostname;
if (!userId) {
const newUserId = await initializeUser(locationAddress);
setUserId(newUserId);
}
await recordQuery(userId || "", locationAddress);
} catch (error) {
console.log("Error updating click count");
}
setIsSubmitting(true);
setSubmitStarted(true);

Expand Down
14 changes: 12 additions & 2 deletions src/component/OracleTestor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import CheckItemFeeds from "./common/CheckItemFeeds";
import { Asset } from "../hooks/types";
import useOracleDeploymentCheck from "../hooks/testor/useOracleDeploymentCheck";
import CheckItemDeployment from "./common/CheckItemDeployment";
import { initializeUser, recordQuery } from "../services/rate/userClick";

const ethLogo = "https://cdn.morpho.org/assets/chains/eth.svg";
const baseLogo = "https://cdn.morpho.org/assets/chains/base.png";
Expand All @@ -33,7 +34,7 @@ const OracleTestor = () => {
const [submitStarted, setSubmitStarted] = useState(false);
const [formSubmitted, setFormSubmitted] = useState(false);
const [showPayload, setShowPayload] = useState(false);

const [userId, setUserId] = useState<string | null>(null);
const [selectedNetwork, setSelectedNetwork] = useState<{
value: number;
label: JSX.Element;
Expand Down Expand Up @@ -167,7 +168,16 @@ const OracleTestor = () => {
event.preventDefault();

resetState();

try {
const locationAddress = window.location.hostname;
if (!userId) {
const newUserId = await initializeUser(locationAddress);
setUserId(newUserId);
}
await recordQuery(userId || "", locationAddress);
} catch (error) {
console.log("Error updating click count");
}
setIsSubmitting(true);
setSubmitStarted(true);
setFormSubmitted(true);
Expand Down
140 changes: 140 additions & 0 deletions src/services/rate/userClick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
class RedisManager {
private static instance: RedisManager;
private readonly UPSTASH_REDIS_REST_URL: string;
private readonly UPSTASH_REDIS_REST_TOKEN: string;

private constructor() {
this.UPSTASH_REDIS_REST_URL =
process.env.REACT_APP_UPSTASH_REDIS_REST_URL || "";
this.UPSTASH_REDIS_REST_TOKEN =
process.env.REACT_APP_UPSTASH_REDIS_REST_TOKEN || "";

if (!this.UPSTASH_REDIS_REST_URL || !this.UPSTASH_REDIS_REST_TOKEN) {
throw new Error("Redis environment variables are not set");
}
}

public static getInstance(): RedisManager {
if (!RedisManager.instance) {
RedisManager.instance = new RedisManager();
}
return RedisManager.instance;
}

private getAuthHeaders() {
return {
Authorization: `Bearer ${this.UPSTASH_REDIS_REST_TOKEN}`,
};
}

public async getRedisValue(key: string): Promise<string | null> {
try {
const response = await fetch(
`${this.UPSTASH_REDIS_REST_URL}/get/${key}`,
{
headers: this.getAuthHeaders(),
}
);
if (!response.ok) return null;
const data = await response.json();
return data.result;
} catch {
return null;
}
}

public async incrementRedisValue(key: string): Promise<number | null> {
try {
const response = await fetch(
`${this.UPSTASH_REDIS_REST_URL}/incr/${key}`,
{
headers: this.getAuthHeaders(),
}
);
if (!response.ok) return null;
const data = await response.json();
return data.result;
} catch {
return null;
}
}

public async setRedisValue(key: string, value: string): Promise<void> {
try {
await fetch(`${this.UPSTASH_REDIS_REST_URL}/set/${key}/${value}`, {
headers: this.getAuthHeaders(),
});
} catch {
// Silently fail
}
}

public async getUserIdByLocation(
locationAddress: string
): Promise<string | null> {
const totalUsers = await this.getRedisValue("totalUsers");
if (totalUsers === null) return null;

for (let i = 1; i <= parseInt(totalUsers); i++) {
const userId = `user:${i}`;
const storedLocation = await this.getRedisValue(`${userId}:location`);
if (storedLocation === locationAddress) {
return userId;
}
}
return null;
}

public async initializeUser(locationAddress: string): Promise<string | null> {
try {
// Check if user already exists
const existingUserId = await this.getUserIdByLocation(locationAddress);
if (existingUserId) {
return existingUserId;
}

const totalUsers = await this.incrementRedisValue("totalUsers");
if (totalUsers === null) return null;
const userId = `user:${totalUsers}`;
await this.setRedisValue(`${userId}:location`, locationAddress);
await this.setRedisValue(`${userId}:queries`, "0");
await this.setRedisValue(`${userId}:createdAt`, new Date().toISOString());
return userId;
} catch {
return null;
}
}

public async recordQuery(
userId: string,
locationAddress: string
): Promise<void> {
try {
const date = new Date().toISOString().split("T")[0]; // Get current date in YYYY-MM-DD format
await this.incrementRedisValue(`${userId}:queries`);
await this.incrementRedisValue(`${userId}:queries:${date}`);
await this.incrementRedisValue("totalQueries");
await this.setRedisValue(`${userId}:location`, locationAddress);
await this.setRedisValue(
`${userId}:lastQueryAt`,
new Date().toISOString()
);
} catch {
// Silently fail
}
}
}

// Export functions that use the RedisManager instance
export const initializeUser = async (
locationAddress: string
): Promise<string | null> => {
return RedisManager.getInstance().initializeUser(locationAddress);
};

export const recordQuery = async (
userId: string,
locationAddress: string
): Promise<void> => {
return RedisManager.getInstance().recordQuery(userId, locationAddress);
};
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3597,6 +3597,13 @@
resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz"
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==

"@upstash/redis@^1.34.0":
version "1.34.0"
resolved "https://registry.yarnpkg.com/@upstash/redis/-/redis-1.34.0.tgz#f32cd53ebeeafbba7eca10f8597a573d5a2fed0d"
integrity sha512-TrXNoJLkysIl8SBc4u9bNnyoFYoILpCcFJcLyWCccb/QSUmaVKdvY0m5diZqc3btExsapcMbaw/s/wh9Sf1pJw==
dependencies:
crypto-js "^4.2.0"

"@vercel/nft@^0.27.0", "@vercel/nft@^0.27.1":
version "0.27.2"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.27.2.tgz#b5f7881a1c33b813fdc83e7112082411d2eb524b"
Expand Down Expand Up @@ -5665,6 +5672,11 @@ crypto-browserify@^3.12.0:
randombytes "^2.0.0"
randomfill "^1.0.3"

crypto-js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==

crypto-random-string@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz"
Expand Down