Skip to content

Commit bed3b2e

Browse files
authored
Merge pull request #2 from lets-fucking-game/feat/contract-refacto
Feat: Refactoring Contract and adding GiveawayV1 contract
2 parents e7c5cdb + e4f6783 commit bed3b2e

199 files changed

Lines changed: 24443 additions & 5602 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
"private": true,
55
"scripts": {
66
"dev": "yarn workspace lets-fucking-game-dapp dev",
7-
"build": "(yarn run 'build:hardhat' &) && yarn compile:hardhat && yarn build:dapp",
7+
"build": "(yarn run 'build:hardhat' &) && yarn compile:hardhat && yarn build:dapp && yarn build:apis",
88
"compile:hardhat": "yarn workspace lets-fucking-game-hardhat compile:force",
99
"build:hardhat": "yarn workspace lets-fucking-game-hardhat build",
1010
"build:dapp": "yarn workspace lets-fucking-game-dapp build",
11+
"build:apis": "yarn build:apis:giveaway",
12+
"build:apis:giveaway": "yarn workspace lets-fucking-game-apis-giveaways build",
1113
"export": "yarn workspace lets-fucking-game-dapp export",
1214
"start": "yarn workspace lets-fucking-game-dapp start",
1315
"chain": "yarn workspace lets-fucking-game-hardhat chain:force",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# https://editorconfig.org
2+
# Prettier & EditorConfig are adering to Wrangler codebase standards which
3+
# are documented here: https://github.com/cloudflare/wrangler2/pull/1309
4+
root = true
5+
6+
[*]
7+
end_of_line = lf
8+
indent_style = tab
9+
tab_width = 2

packages/apis/giveaways/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dist
2+
node_modules
3+
transpiled
4+
/.idea/
5+
6+
coverage/*
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"printWidth": 80,
3+
"singleQuote": false,
4+
"semi": true,
5+
"useTabs": true
6+
}

packages/apis/giveaways/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# ʕ •́؈•̀) `worker-typescript-template`
2+
3+
## ⚠️ Warning: This template is no longer maintained
4+
5+
Create TypeScript workers in the [command-line with Wrangler 2.0.0+](https://developers.cloudflare.com/workers/wrangler/get-started/) using `wrangler init` instead.
6+
7+
---
8+
9+
A batteries included, with the latest Wrangler, template for kick starting a TypeScript Cloudflare worker project.
10+
11+
## Note: You must use [wrangler](https://developers.cloudflare.com/workers/cli-wrangler/install-update) 1.17 or newer to use this template.
12+
13+
## 🔋 Getting Started
14+
15+
This template is meant to be used with [Wrangler](https://github.com/cloudflare/wrangler). If you are not already familiar with the tool, we recommend that you install the tool and configure it to work with your [Cloudflare account](https://dash.cloudflare.com). Documentation can be found [here](https://developers.cloudflare.com/workers/tooling/wrangler/).
16+
17+
To generate using Wrangler, run this command:
18+
19+
```bash
20+
wrangler generate my-ts-project https://github.com/cloudflare/worker-typescript-template
21+
```
22+
23+
### 👩 💻 Developing
24+
25+
[`src/index.ts`](./src/index.ts) calls the request handler in [`src/handler.ts`](./src/handler.ts), and will return the [request method](https://developer.mozilla.org/en-US/docs/Web/API/Request/method) for the given request.
26+
27+
### 🧪 Testing
28+
29+
This template comes with jest tests utilizing [Miniflare](https://github.com/cloudflare/miniflare) which simply test that the request handler can handle each request method. `npm test` will run your tests.
30+
31+
### ✏️ Formatting
32+
33+
This template uses [`prettier`](https://prettier.io/) to format the project. To invoke, run `npm run format`.
34+
35+
### 👀 Previewing and Publishing
36+
37+
For information on how to preview and publish your worker, please see the [Wrangler docs](https://developers.cloudflare.com/workers/tooling/wrangler/commands/#publish).
38+
39+
## 🤢 Issues
40+
41+
If you run into issues with this specific project, please feel free to file an issue [here](https://github.com/cloudflare/worker-typescript-template/issues). If the problem is with Wrangler, please file an issue [here](https://github.com/cloudflare/wrangler/issues).
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"preset": "ts-jest/presets/default-esm",
3+
"globals": {
4+
"ts-jest": {
5+
"tsconfig": "./tsconfig.json",
6+
"useESM": true
7+
}
8+
},
9+
"transform": {
10+
"^.+\\.(t|j)sx?$": "ts-jest"
11+
},
12+
"testRegex": "/test/.*\\.test\\.ts$",
13+
"testEnvironment": "miniflare",
14+
"testEnvironmentOptions": {
15+
"scriptPath": "./src/index.ts",
16+
"modules": true
17+
},
18+
"collectCoverageFrom": ["src/**/*.{ts,tsx}"]
19+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "giveaways",
3+
"version": "0.0.1",
4+
"description": "Cloudflare worker giveaway API",
5+
"scripts": {
6+
"dev": "npx wrangler dev",
7+
"publish": "npx wrangler publish",
8+
"format": "prettier --write '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
9+
"lint": "eslint --max-warnings=0 src && prettier --check '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
10+
"test": "concurrently --success first --kill-others \"wrangler dev\" \"wait-on -d 5000 -t 30000 http-get://127.0.0.1:8787 && node --experimental-vm-modules node_modules/.bin/jest\"",
11+
"test:coverage": "concurrently --success first --kill-others \"wrangler dev\" \"wait-on -d 5000 -t 30000 http-get://127.0.0.1:8787 && node --experimental-vm-modules node_modules/.bin/jest --coverage\""
12+
},
13+
"author": "author",
14+
"license": "MIT OR Apache-2.0",
15+
"eslintConfig": {
16+
"root": true,
17+
"extends": [
18+
"typescript",
19+
"prettier"
20+
]
21+
},
22+
"dependencies": {
23+
"crypto": "^1.0.1",
24+
"ethers": "^5.7.2",
25+
"itty-router": "^2.6.1",
26+
"itty-router-extras": "^0.4.2",
27+
"needle": "3.2.0",
28+
"query-string": "^7.1.3",
29+
"twitter-api-sdk": "^1.1.1"
30+
},
31+
"devDependencies": {
32+
"@cloudflare/workers-types": "^3.14.1",
33+
"@types/itty-router-extras": "^0.4.0",
34+
"@types/jest": "^28.1.6",
35+
"@typescript-eslint/eslint-plugin": "^5.32.0",
36+
"@typescript-eslint/parser": "^5.32.0",
37+
"concurrently": "^7.6.0",
38+
"eslint": "^8.21.0",
39+
"eslint-config-prettier": "^8.5.0",
40+
"eslint-config-typescript": "^3.0.0",
41+
"jest": "^28.1.3",
42+
"jest-environment-miniflare": "^2.6.0",
43+
"miniflare": "^2.6.0",
44+
"prettier": "^2.7.1",
45+
"ts-jest": "^28.0.7",
46+
"typescript": "^4.7.4",
47+
"wait-on": "6.0.1",
48+
"wrangler": "^2.6.1"
49+
}
50+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export {};
2+
3+
declare global {
4+
const TWITTER_BEARER_TOKEN: string;
5+
const TWITTER_BASE_URL: string;
6+
const USERS: KVNamespace;
7+
const KV_CACHE: boolean;
8+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/* eslint-disable camelcase */
2+
3+
import crypto from "crypto";
4+
5+
import { UserKV } from "./kv";
6+
import { range, randomNumber, randomNewNumber } from "./utils";
7+
import { getRequestParams } from "./helper";
8+
9+
import { stringify } from "query-string";
10+
11+
const baseUri = TWITTER_BASE_URL;
12+
13+
export const getAllRetweets = async (tweetId: string) => {
14+
if (!tweetId) throw new Error("tweet id is needed");
15+
16+
let result = [];
17+
let isNextResults = true;
18+
let pagination = "";
19+
20+
while (isNextResults) {
21+
const paginateResults = await getRetweets(tweetId, pagination);
22+
result = result.concat(paginateResults?.data);
23+
isNextResults = !!paginateResults?.meta?.next_token;
24+
pagination = paginateResults?.meta?.next_token;
25+
}
26+
27+
return result;
28+
};
29+
30+
export const getRetweets = async (tweetId: string, pagination_token = "") => {
31+
if (!tweetId) throw new Error("tweet id is needed");
32+
33+
const endpointURL = `${baseUri}/2/tweets/${tweetId}/retweeted_by`;
34+
35+
const params = {
36+
"user.fields": "id,profile_image_url,username",
37+
max_results: 100,
38+
};
39+
40+
if (pagination_token) params.pagination_token = pagination_token;
41+
42+
const endpointWithParamsURL = `${endpointURL}?${stringify(params)}`;
43+
44+
const res = await fetch(endpointWithParamsURL, getRequestParams());
45+
46+
if (res.ok) return await res.json();
47+
48+
throw new Error("Unsuccessful request");
49+
};
50+
51+
export const getTweet = async (tweetId: string) => {
52+
if (!tweetId) throw new Error("tweet id is needed");
53+
54+
const endpointURL = `${baseUri}/2/tweets/${tweetId}`;
55+
56+
const res = await fetch(endpointURL, getRequestParams());
57+
if (res.ok) return await res.json();
58+
59+
throw new Error("Unsuccessful request");
60+
};
61+
62+
export const signUp = async (
63+
userId: string,
64+
userAddress: string,
65+
accessToken: string
66+
) => {
67+
if (!userId) throw new Error("user id is needed");
68+
if (!userAddress) throw new Error("user address is needed");
69+
if (!accessToken) throw new Error("user access token is needed");
70+
71+
const user = await UserKV.getUser(userId);
72+
if (!user) throw new Error("no user found");
73+
if (checkUserSignUp(userId)) throw new Error("user already sign up");
74+
if (user.accessToken !== accessToken) throw new Error("accessToken mismatch");
75+
76+
const userData = {
77+
...user,
78+
isRegistered: true,
79+
userAddress,
80+
};
81+
82+
UserKV.saveUser(userId, userData);
83+
return {
84+
userId,
85+
...userData,
86+
};
87+
};
88+
89+
export const checkUserSignUp = async (userId: string) => {
90+
const user = await UserKV.getUser(userId);
91+
return user?.isRegistered || false;
92+
};
93+
94+
export const drawWinners = async (
95+
giveawayId: string,
96+
tweetId: string,
97+
retweetMaxCount: number,
98+
prizes: number
99+
) => {
100+
if (!giveawayId) throw new Error("giveaway id is needed");
101+
if (!tweetId) throw new Error("tweet id is needed");
102+
if (typeof retweetMaxCount === "undefined")
103+
throw new Error("retweetMaxCount id is needed");
104+
if (!prizes) throw new Error("prizes id is needed");
105+
106+
const retweets = await getAllRetweets(tweetId);
107+
108+
// TODO order retweets by date if needed ?
109+
110+
const winners = [],
111+
randoms = [],
112+
positions = [];
113+
const retweetCountLimit =
114+
retweetMaxCount && retweets.length > retweetMaxCount
115+
? retweetMaxCount
116+
: retweets.length;
117+
118+
for (let i = 0; i < +prizes; i++) {
119+
const random = randomNewNumber(0, retweetCountLimit, randoms);
120+
winners.push(retweets[random].id);
121+
positions.push(i);
122+
randoms.push(random);
123+
}
124+
125+
return {
126+
winners,
127+
positions,
128+
};
129+
};
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { Obj } from "itty-router";
2+
import { error } from "itty-router-extras";
3+
import { UserKV } from "./kv";
4+
5+
export const requireField = (field: string, params: Obj | undefined) => {
6+
if (!params) {
7+
return error(400, "Invalid params");
8+
}
9+
const value = params[field];
10+
if (!value) {
11+
return error(400, "Invalid field", field);
12+
}
13+
return null;
14+
};
15+
16+
function isString(s: any): s is string {
17+
return typeof s === "string" || s instanceof String;
18+
}
19+
20+
export const isOriginAllowed = (origin: string | null, allowedOrigin: any) => {
21+
if (Array.isArray(allowedOrigin)) {
22+
for (let i = 0; i < allowedOrigin.length; ++i) {
23+
if (isOriginAllowed(origin, allowedOrigin[i])) {
24+
return true;
25+
}
26+
}
27+
return false;
28+
}
29+
if (isString(allowedOrigin)) {
30+
return origin === allowedOrigin;
31+
}
32+
if (origin && allowedOrigin instanceof RegExp) {
33+
return allowedOrigin.test(origin);
34+
}
35+
return !!allowedOrigin;
36+
};
37+
38+
export const handleCors = (allowedOrigin: any) => (request: Request) => {
39+
const reqOrigin = request.headers.get("origin");
40+
const isAllowed = isOriginAllowed(reqOrigin, allowedOrigin);
41+
const methods = `GET, HEAD, OPTIONS`;
42+
const headers = `referer, origin, content-type`;
43+
if (isAllowed && reqOrigin) {
44+
const corsHeaders = {
45+
"Access-Control-Allow-Origin": reqOrigin,
46+
"Access-Control-Allow-Methods": methods,
47+
"Access-Control-Allow-Headers": headers,
48+
};
49+
// Handle CORS pre-flight request.
50+
return new Response(null, {
51+
status: 204,
52+
headers: corsHeaders,
53+
});
54+
}
55+
console.info("Origin not allowed from Cors", reqOrigin);
56+
// Handle standard OPTIONS request.
57+
return new Response(null, {
58+
headers: {
59+
Allow: methods,
60+
},
61+
});
62+
};
63+
64+
export const wrapCorsHeader = (
65+
request: Request,
66+
response: Response,
67+
options: any = {}
68+
) => {
69+
const { allowedOrigin = "*" } = options;
70+
const reqOrigin = request.headers.get("origin");
71+
const isAllowed = isOriginAllowed(reqOrigin, allowedOrigin);
72+
if (isAllowed && reqOrigin) {
73+
response.headers.set("Access-Control-Allow-Origin", reqOrigin);
74+
}
75+
console.info("Origin not allowed", reqOrigin);
76+
77+
return response;
78+
};
79+
80+
export const getAppBearerToken = () => {
81+
const token = TWITTER_BEARER_TOKEN;
82+
83+
// generate new BEARER TOKEN if needed
84+
// const cached = KV_CACHE && (await UserKV.getBearerToken());
85+
// if (
86+
// !cached ||
87+
// Date.now() - new Date(cached.updatedAt).getTime() > 2 * 1000 * 60
88+
// ) {
89+
// if (KV_CACHE) {
90+
// console.info("no cached found!");
91+
// }
92+
// console.info("refreshing bearer token");
93+
// }
94+
95+
return token;
96+
};
97+
98+
export const getRequestParams = () => {
99+
return {
100+
headers: {
101+
"User-Agent": "LFGames",
102+
authorization: `Bearer ${getAppBearerToken()}`,
103+
},
104+
};
105+
};

0 commit comments

Comments
 (0)