├── .gitignore
├── .npmrc
├── README.md
├── apps
├── api
│ ├── .gitignore
│ ├── README.md
│ ├── config.ts
│ ├── index.ts
│ ├── middleware.ts
│ ├── package.json
│ ├── tsconfig.json
│ └── types.d.ts
├── frontend
│ ├── .gitignore
│ ├── README.md
│ ├── app
│ │ ├── dashboard
│ │ │ └── page.tsx
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components.json
│ ├── components
│ │ ├── Appbar.tsx
│ │ ├── theme-provider.tsx
│ │ └── ui
│ │ │ └── button.tsx
│ ├── config.ts
│ ├── eslint.config.mjs
│ ├── hooks
│ │ └── useWebsites.tsx
│ ├── lib
│ │ └── utils.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── public
│ │ ├── file.svg
│ │ ├── globe.svg
│ │ ├── next.svg
│ │ ├── vercel.svg
│ │ └── window.svg
│ └── tsconfig.json
├── hub
│ ├── .gitignore
│ ├── README.md
│ ├── index.ts
│ ├── package.json
│ └── tsconfig.json
└── validator
│ ├── .gitignore
│ ├── README.md
│ ├── index.ts
│ ├── package.json
│ └── tsconfig.json
├── bun.lock
├── package-lock.json
├── package.json
├── packages
├── common
│ ├── .gitignore
│ ├── README.md
│ ├── index.ts
│ ├── package.json
│ └── tsconfig.json
├── db
│ ├── .gitignore
│ ├── README.md
│ ├── index.ts
│ ├── package.json
│ ├── prisma
│ │ ├── migrations
│ │ │ ├── 20250314011543_init
│ │ │ │ └── migration.sql
│ │ │ ├── 20250314012859_added_disabled
│ │ │ │ └── migration.sql
│ │ │ ├── 20250315042613_added_payout
│ │ │ │ └── migration.sql
│ │ │ ├── 20250315042714_fix_payout
│ │ │ │ └── migration.sql
│ │ │ └── migration_lock.toml
│ │ ├── schema.prisma
│ │ └── seed.ts
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
├── eslint-config
│ ├── README.md
│ ├── base.js
│ ├── next.js
│ ├── package.json
│ └── react-internal.js
├── typescript-config
│ ├── base.json
│ ├── nextjs.json
│ ├── package.json
│ └── react-library.json
└── ui
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ ├── button.tsx
│ ├── card.tsx
│ └── code.tsx
│ ├── tsconfig.json
│ └── turbo
│ └── generators
│ ├── config.ts
│ └── templates
│ └── component.hbs
└── turbo.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # Dependencies
4 | node_modules
5 | .pnp
6 | .pnp.js
7 |
8 | # Local env files
9 | .env
10 | .env.local
11 | .env.development.local
12 | .env.test.local
13 | .env.production.local
14 |
15 | # Testing
16 | coverage
17 |
18 | # Turbo
19 | .turbo
20 |
21 | # Vercel
22 | .vercel
23 |
24 | # Build Outputs
25 | .next/
26 | out/
27 | build
28 | dist
29 |
30 |
31 | # Debug
32 | npm-debug.log*
33 | yarn-debug.log*
34 | yarn-error.log*
35 |
36 | # Misc
37 | .DS_Store
38 | *.pem
39 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code100x/dpin-uptime/fc49183c2aef51e7dd871f98c943b62b4ac6a02c/.npmrc
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Turborepo starter
2 |
3 | This Turborepo starter is maintained by the Turborepo core team.
4 |
5 | ## Using this example
6 |
7 | Run the following command:
8 |
9 | ```sh
10 | npx create-turbo@latest
11 | ```
12 |
13 | ## What's inside?
14 |
15 | This Turborepo includes the following packages/apps:
16 |
17 | ### Apps and Packages
18 |
19 | - `docs`: a [Next.js](https://nextjs.org/) app
20 | - `web`: another [Next.js](https://nextjs.org/) app
21 | - `@repo/ui`: a stub React component library shared by both `web` and `docs` applications
22 | - `@repo/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
23 | - `@repo/typescript-config`: `tsconfig.json`s used throughout the monorepo
24 |
25 | Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
26 |
27 | ### Utilities
28 |
29 | This Turborepo has some additional tools already setup for you:
30 |
31 | - [TypeScript](https://www.typescriptlang.org/) for static type checking
32 | - [ESLint](https://eslint.org/) for code linting
33 | - [Prettier](https://prettier.io) for code formatting
34 |
35 | ### Build
36 |
37 | To build all apps and packages, run the following command:
38 |
39 | ```
40 | cd my-turborepo
41 | pnpm build
42 | ```
43 |
44 | ### Develop
45 |
46 | To develop all apps and packages, run the following command:
47 |
48 | ```
49 | cd my-turborepo
50 | pnpm dev
51 | ```
52 |
53 | ### Remote Caching
54 |
55 | > [!TIP]
56 | > Vercel Remote Cache is free for all plans. Get started today at [vercel.com](https://vercel.com/signup?/signup?utm_source=remote-cache-sdk&utm_campaign=free_remote_cache).
57 |
58 | Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines.
59 |
60 | By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup?utm_source=turborepo-examples), then enter the following commands:
61 |
62 | ```
63 | cd my-turborepo
64 | npx turbo login
65 | ```
66 |
67 | This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview).
68 |
69 | Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo:
70 |
71 | ```
72 | npx turbo link
73 | ```
74 |
75 | ## Useful Links
76 |
77 | Learn more about the power of Turborepo:
78 |
79 | - [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks)
80 | - [Caching](https://turbo.build/repo/docs/core-concepts/caching)
81 | - [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching)
82 | - [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
83 | - [Configuration Options](https://turbo.build/repo/docs/reference/configuration)
84 | - [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference)
85 |
--------------------------------------------------------------------------------
/apps/api/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | # Logs
4 |
5 | logs
6 | _.log
7 | npm-debug.log_
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 | .pnpm-debug.log*
12 |
13 | # Caches
14 |
15 | .cache
16 |
17 | # Diagnostic reports (https://nodejs.org/api/report.html)
18 |
19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
20 |
21 | # Runtime data
22 |
23 | pids
24 | _.pid
25 | _.seed
26 | *.pid.lock
27 |
28 | # Directory for instrumented libs generated by jscoverage/JSCover
29 |
30 | lib-cov
31 |
32 | # Coverage directory used by tools like istanbul
33 |
34 | coverage
35 | *.lcov
36 |
37 | # nyc test coverage
38 |
39 | .nyc_output
40 |
41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
42 |
43 | .grunt
44 |
45 | # Bower dependency directory (https://bower.io/)
46 |
47 | bower_components
48 |
49 | # node-waf configuration
50 |
51 | .lock-wscript
52 |
53 | # Compiled binary addons (https://nodejs.org/api/addons.html)
54 |
55 | build/Release
56 |
57 | # Dependency directories
58 |
59 | node_modules/
60 | jspm_packages/
61 |
62 | # Snowpack dependency directory (https://snowpack.dev/)
63 |
64 | web_modules/
65 |
66 | # TypeScript cache
67 |
68 | *.tsbuildinfo
69 |
70 | # Optional npm cache directory
71 |
72 | .npm
73 |
74 | # Optional eslint cache
75 |
76 | .eslintcache
77 |
78 | # Optional stylelint cache
79 |
80 | .stylelintcache
81 |
82 | # Microbundle cache
83 |
84 | .rpt2_cache/
85 | .rts2_cache_cjs/
86 | .rts2_cache_es/
87 | .rts2_cache_umd/
88 |
89 | # Optional REPL history
90 |
91 | .node_repl_history
92 |
93 | # Output of 'npm pack'
94 |
95 | *.tgz
96 |
97 | # Yarn Integrity file
98 |
99 | .yarn-integrity
100 |
101 | # dotenv environment variable files
102 |
103 | .env
104 | .env.development.local
105 | .env.test.local
106 | .env.production.local
107 | .env.local
108 |
109 | # parcel-bundler cache (https://parceljs.org/)
110 |
111 | .parcel-cache
112 |
113 | # Next.js build output
114 |
115 | .next
116 | out
117 |
118 | # Nuxt.js build / generate output
119 |
120 | .nuxt
121 | dist
122 |
123 | # Gatsby files
124 |
125 | # Comment in the public line in if your project uses Gatsby and not Next.js
126 |
127 | # https://nextjs.org/blog/next-9-1#public-directory-support
128 |
129 | # public
130 |
131 | # vuepress build output
132 |
133 | .vuepress/dist
134 |
135 | # vuepress v2.x temp and cache directory
136 |
137 | .temp
138 |
139 | # Docusaurus cache and generated files
140 |
141 | .docusaurus
142 |
143 | # Serverless directories
144 |
145 | .serverless/
146 |
147 | # FuseBox cache
148 |
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 |
153 | .dynamodb/
154 |
155 | # TernJS port file
156 |
157 | .tern-port
158 |
159 | # Stores VSCode versions used for testing VSCode extensions
160 |
161 | .vscode-test
162 |
163 | # yarn v2
164 |
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.*
170 |
171 | # IntelliJ based IDEs
172 | .idea
173 |
174 | # Finder (MacOS) folder config
175 | .DS_Store
176 |
--------------------------------------------------------------------------------
/apps/api/README.md:
--------------------------------------------------------------------------------
1 | # api
2 |
3 | To install dependencies:
4 |
5 | ```bash
6 | bun install
7 | ```
8 |
9 | To run:
10 |
11 | ```bash
12 | bun run index.ts
13 | ```
14 |
15 | This project was created using `bun init` in bun v1.2.2. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
16 |
--------------------------------------------------------------------------------
/apps/api/config.ts:
--------------------------------------------------------------------------------
1 |
2 | export const JWT_PUBLIC_KEY = process.env.JWT_PUBLIC_KEY || `-----BEGIN PUBLIC KEY-----
3 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxYRjjCo+ZhdDrsVduRaj
4 | o4U9XNYbUxG4LBgtrHUpvMrRda6yXXfuecC0zwdF4O16ZNp9LMyy2bllXG8HOWyw
5 | ac/r/9Zt98A9jMUEKq/AhRvaRaavr9jMFceJNTebFMOnzPRZGIhMWWOGPsrwqWLa
6 | JbMtnAd2IlQc4MNJmns2NeaPmujDysZDmN5BwLd0LzTaktOFsrVCFYu7MiL7sYi9
7 | 229OovGxu55Y1qpekwt42nuipbpZEx59FdAIpJCgjsp8GeqavC5ySWuhRbKGTO+e
8 | qiBV0S9m134FuGRc67HEYcr7BkTSFwF4pD/L2culHEBaRedilGdRbNAGFAQhN4mS
9 | yQIDAQAB
10 | -----END PUBLIC KEY-----
11 | `;
--------------------------------------------------------------------------------
/apps/api/index.ts:
--------------------------------------------------------------------------------
1 | import express from "express"
2 | import { authMiddleware } from "./middleware";
3 | import { prismaClient } from "db/client";
4 | import cors from "cors";
5 | import { Transaction, SystemProgram, Connection } from "@solana/web3.js";
6 |
7 |
8 | const connection = new Connection("https://api.mainnet-beta.solana.com");
9 | const app = express();
10 |
11 | app.use(cors());
12 | app.use(express.json());
13 |
14 | app.post("/api/v1/website", authMiddleware, async (req, res) => {
15 | const userId = req.userId!;
16 | const { url } = req.body;
17 |
18 | const data = await prismaClient.website.create({
19 | data: {
20 | userId,
21 | url
22 | }
23 | })
24 |
25 | res.json({
26 | id: data.id
27 | })
28 | })
29 |
30 | app.get("/api/v1/website/status", authMiddleware, async (req, res) => {
31 | const websiteId = req.query.websiteId! as unknown as string;
32 | const userId = req.userId;
33 |
34 | const data = await prismaClient.website.findFirst({
35 | where: {
36 | id: websiteId,
37 | userId,
38 | disabled: false
39 | },
40 | include: {
41 | ticks: true
42 | }
43 | })
44 |
45 | res.json(data)
46 |
47 | })
48 |
49 | app.get("/api/v1/websites", authMiddleware, async (req, res) => {
50 | const userId = req.userId!;
51 |
52 | const websites = await prismaClient.website.findMany({
53 | where: {
54 | userId,
55 | disabled: false
56 | },
57 | include: {
58 | ticks: true
59 | }
60 | })
61 |
62 | res.json({
63 | websites
64 | })
65 | })
66 |
67 | app.delete("/api/v1/website/", authMiddleware, async (req, res) => {
68 | const websiteId = req.body.websiteId;
69 | const userId = req.userId!;
70 |
71 | await prismaClient.website.update({
72 | where: {
73 | id: websiteId,
74 | userId
75 | },
76 | data: {
77 | disabled: true
78 | }
79 | })
80 |
81 | res.json({
82 | message: "Deleted website successfully"
83 | })
84 | })
85 |
86 | app.post("/api/v1/payout/:validatorId", async (req, res) => {
87 |
88 | })
89 |
90 | app.listen(8080);
91 |
--------------------------------------------------------------------------------
/apps/api/middleware.ts:
--------------------------------------------------------------------------------
1 | import type { NextFunction, Request, Response } from "express";
2 | import jwt from "jsonwebtoken";
3 | import { JWT_PUBLIC_KEY } from "./config";
4 |
5 | export function authMiddleware(req: Request, res: Response, next: NextFunction) {
6 | const token = req.headers['authorization'];
7 | if (!token) {
8 | return res.status(401).json({ error: 'Unauthorized' });
9 | }
10 |
11 | const decoded = jwt.verify(token, JWT_PUBLIC_KEY);
12 | console.log(decoded);
13 | if (!decoded || !decoded.sub) {
14 | return res.status(401).json({ error: 'Unauthorized' });
15 | }
16 |
17 | req.userId = decoded.sub as string;
18 |
19 | next()
20 | }
--------------------------------------------------------------------------------
/apps/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "api",
3 | "module": "index.ts",
4 | "type": "module",
5 | "devDependencies": {
6 | "@types/bun": "latest"
7 | },
8 | "peerDependencies": {
9 | "typescript": "^5.0.0"
10 | },
11 | "dependencies": {
12 | "@solana/web3.js": "^1.98.0",
13 | "@types/cors": "^2.8.17",
14 | "@types/express": "^5.0.0",
15 | "@types/jsonwebtoken": "^9.0.9",
16 | "cors": "^2.8.5",
17 | "db": "*",
18 | "express": "^4.21.2",
19 | "jsonwebtoken": "^9.0.2",
20 | "jwt": "^0.2.0"
21 | }
22 | }
--------------------------------------------------------------------------------
/apps/api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Enable latest features
4 | "lib": ["ESNext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "jsx": "react-jsx",
9 | "allowJs": true,
10 |
11 | // Bundler mode
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": true,
15 | "noEmit": true,
16 |
17 | // Best practices
18 | "strict": true,
19 | "skipLibCheck": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | // Some stricter flags (disabled by default)
23 | "noUnusedLocals": false,
24 | "noUnusedParameters": false,
25 | "noPropertyAccessFromIndexSignature": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apps/api/types.d.ts:
--------------------------------------------------------------------------------
1 |
2 | declare namespace Express {
3 | interface Request {
4 | userId?: string
5 | }
6 | }
--------------------------------------------------------------------------------
/apps/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env*
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | next-env.d.ts
42 |
--------------------------------------------------------------------------------
/apps/frontend/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
37 |
--------------------------------------------------------------------------------
/apps/frontend/app/dashboard/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useMemo } from 'react';
3 | import { ChevronDown, ChevronUp, Globe, Plus, Moon, Sun } from 'lucide-react';
4 | import { useWebsites } from '@/hooks/useWebsites';
5 | import axios from 'axios';
6 | import { API_BACKEND_URL } from '@/config';
7 | import { useAuth } from '@clerk/nextjs';
8 |
9 | type UptimeStatus = "good" | "bad" | "unknown";
10 |
11 | function StatusCircle({ status }: { status: UptimeStatus }) {
12 | return (
13 |
14 | );
15 | }
16 |
17 | function UptimeTicks({ ticks }: { ticks: UptimeStatus[] }) {
18 | return (
19 |
20 | {ticks.map((tick, index) => (
21 |
27 | ))}
28 |
29 | );
30 | }
31 |
32 | function CreateWebsiteModal({ isOpen, onClose }: { isOpen: boolean; onClose: (url: string | null) => void }) {
33 | const [url, setUrl] = useState('');
34 | if (!isOpen) return null;
35 |
36 | return (
37 |
38 |
39 |
Add New Website
40 |
41 |
44 | setUrl(e.target.value)}
50 | />
51 |
52 |
53 |
60 |
67 |
68 |
69 |
70 | );
71 | }
72 |
73 | interface ProcessedWebsite {
74 | id: string;
75 | url: string;
76 | status: UptimeStatus;
77 | uptimePercentage: number;
78 | lastChecked: string;
79 | uptimeTicks: UptimeStatus[];
80 | }
81 |
82 | function WebsiteCard({ website }: { website: ProcessedWebsite }) {
83 | const [isExpanded, setIsExpanded] = useState(false);
84 |
85 | return (
86 |
87 |
setIsExpanded(!isExpanded)}
90 | >
91 |
92 |
93 |
94 |
{website.url}
95 |
96 |
97 |
98 |
99 | {website.uptimePercentage.toFixed(1)}% uptime
100 |
101 | {isExpanded ? (
102 |
103 | ) : (
104 |
105 | )}
106 |
107 |
108 |
109 | {isExpanded && (
110 |
111 |
112 |
Last 30 minutes status:
113 |
114 |
115 |
116 | Last checked: {website.lastChecked}
117 |
118 |
119 | )}
120 |
121 | );
122 | }
123 |
124 | function App() {
125 | const [isDarkMode, setIsDarkMode] = useState(false);
126 | const [isModalOpen, setIsModalOpen] = useState(false);
127 | const {websites, refreshWebsites} = useWebsites();
128 | const { getToken } = useAuth();
129 |
130 | const processedWebsites = useMemo(() => {
131 | return websites.map(website => {
132 | // Sort ticks by creation time
133 | const sortedTicks = [...website.ticks].sort((a, b) =>
134 | new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
135 | );
136 |
137 | // Get the most recent 30 minutes of ticks
138 | const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000);
139 | const recentTicks = sortedTicks.filter(tick =>
140 | new Date(tick.createdAt) > thirtyMinutesAgo
141 | );
142 |
143 | // Aggregate ticks into 3-minute windows (10 windows total)
144 | const windows: UptimeStatus[] = [];
145 |
146 | for (let i = 0; i < 10; i++) {
147 | const windowStart = new Date(Date.now() - (i + 1) * 3 * 60 * 1000);
148 | const windowEnd = new Date(Date.now() - i * 3 * 60 * 1000);
149 |
150 | const windowTicks = recentTicks.filter(tick => {
151 | const tickTime = new Date(tick.createdAt);
152 | return tickTime >= windowStart && tickTime < windowEnd;
153 | });
154 |
155 | // Window is considered up if majority of ticks are up
156 | const upTicks = windowTicks.filter(tick => tick.status === 'Good').length;
157 | windows[9 - i] = windowTicks.length === 0 ? "unknown" : (upTicks / windowTicks.length) >= 0.5 ? "good" : "bad";
158 | }
159 |
160 | // Calculate overall status and uptime percentage
161 | const totalTicks = sortedTicks.length;
162 | const upTicks = sortedTicks.filter(tick => tick.status === 'Good').length;
163 | const uptimePercentage = totalTicks === 0 ? 100 : (upTicks / totalTicks) * 100;
164 |
165 | // Get the most recent status
166 | const currentStatus = windows[windows.length - 1];
167 |
168 | // Format the last checked time
169 | const lastChecked = sortedTicks[0]
170 | ? new Date(sortedTicks[0].createdAt).toLocaleTimeString()
171 | : 'Never';
172 |
173 | return {
174 | id: website.id,
175 | url: website.url,
176 | status: currentStatus,
177 | uptimePercentage,
178 | lastChecked,
179 | uptimeTicks: windows,
180 | };
181 | });
182 | }, [websites]);
183 |
184 | // Toggle dark mode
185 | React.useEffect(() => {
186 | if (isDarkMode) {
187 | document.documentElement.classList.add('dark');
188 | } else {
189 | document.documentElement.classList.remove('dark');
190 | }
191 | }, [isDarkMode]);
192 |
193 | return (
194 |
195 |
196 |
197 |
198 |
199 |
Uptime Monitor
200 |
201 |
202 |
212 |
219 |
220 |
221 |
222 |
223 | {processedWebsites.map((website) => (
224 |
225 | ))}
226 |
227 |
228 |
229 |
{
232 | if (url === null) {
233 | setIsModalOpen(false);
234 | return;
235 | }
236 |
237 | const token = await getToken();
238 | setIsModalOpen(false)
239 | axios.post(`${API_BACKEND_URL}/api/v1/website`, {
240 | url,
241 | }, {
242 | headers: {
243 | Authorization: token,
244 | },
245 | })
246 | .then(() => {
247 | refreshWebsites();
248 | })
249 | }}
250 | />
251 |
252 | );
253 | }
254 |
255 | export default App;
--------------------------------------------------------------------------------
/apps/frontend/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code100x/dpin-uptime/fc49183c2aef51e7dd871f98c943b62b4ac6a02c/apps/frontend/app/favicon.ico
--------------------------------------------------------------------------------
/apps/frontend/app/globals.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | @plugin "tailwindcss-animate";
4 |
5 | @custom-variant dark (&:is(.dark *));
6 |
7 | @theme inline {
8 | --color-background: var(--background);
9 | --color-foreground: var(--foreground);
10 | --font-sans: var(--font-geist-sans);
11 | --font-mono: var(--font-geist-mono);
12 | --color-sidebar-ring: var(--sidebar-ring);
13 | --color-sidebar-border: var(--sidebar-border);
14 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
15 | --color-sidebar-accent: var(--sidebar-accent);
16 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
17 | --color-sidebar-primary: var(--sidebar-primary);
18 | --color-sidebar-foreground: var(--sidebar-foreground);
19 | --color-sidebar: var(--sidebar);
20 | --color-chart-5: var(--chart-5);
21 | --color-chart-4: var(--chart-4);
22 | --color-chart-3: var(--chart-3);
23 | --color-chart-2: var(--chart-2);
24 | --color-chart-1: var(--chart-1);
25 | --color-ring: var(--ring);
26 | --color-input: var(--input);
27 | --color-border: var(--border);
28 | --color-destructive: var(--destructive);
29 | --color-accent-foreground: var(--accent-foreground);
30 | --color-accent: var(--accent);
31 | --color-muted-foreground: var(--muted-foreground);
32 | --color-muted: var(--muted);
33 | --color-secondary-foreground: var(--secondary-foreground);
34 | --color-secondary: var(--secondary);
35 | --color-primary-foreground: var(--primary-foreground);
36 | --color-primary: var(--primary);
37 | --color-popover-foreground: var(--popover-foreground);
38 | --color-popover: var(--popover);
39 | --color-card-foreground: var(--card-foreground);
40 | --color-card: var(--card);
41 | --radius-sm: calc(var(--radius) - 4px);
42 | --radius-md: calc(var(--radius) - 2px);
43 | --radius-lg: var(--radius);
44 | --radius-xl: calc(var(--radius) + 4px);
45 | }
46 |
47 | :root {
48 | --radius: 0.625rem;
49 | --background: oklch(1 0 0);
50 | --foreground: oklch(0.145 0 0);
51 | --card: oklch(1 0 0);
52 | --card-foreground: oklch(0.145 0 0);
53 | --popover: oklch(1 0 0);
54 | --popover-foreground: oklch(0.145 0 0);
55 | --primary: oklch(0.205 0 0);
56 | --primary-foreground: oklch(0.985 0 0);
57 | --secondary: oklch(0.97 0 0);
58 | --secondary-foreground: oklch(0.205 0 0);
59 | --muted: oklch(0.97 0 0);
60 | --muted-foreground: oklch(0.556 0 0);
61 | --accent: oklch(0.97 0 0);
62 | --accent-foreground: oklch(0.205 0 0);
63 | --destructive: oklch(0.577 0.245 27.325);
64 | --border: oklch(0.922 0 0);
65 | --input: oklch(0.922 0 0);
66 | --ring: oklch(0.708 0 0);
67 | --chart-1: oklch(0.646 0.222 41.116);
68 | --chart-2: oklch(0.6 0.118 184.704);
69 | --chart-3: oklch(0.398 0.07 227.392);
70 | --chart-4: oklch(0.828 0.189 84.429);
71 | --chart-5: oklch(0.769 0.188 70.08);
72 | --sidebar: oklch(0.985 0 0);
73 | --sidebar-foreground: oklch(0.145 0 0);
74 | --sidebar-primary: oklch(0.205 0 0);
75 | --sidebar-primary-foreground: oklch(0.985 0 0);
76 | --sidebar-accent: oklch(0.97 0 0);
77 | --sidebar-accent-foreground: oklch(0.205 0 0);
78 | --sidebar-border: oklch(0.922 0 0);
79 | --sidebar-ring: oklch(0.708 0 0);
80 | }
81 |
82 | .dark {
83 | --background: oklch(0.145 0 0);
84 | --foreground: oklch(0.985 0 0);
85 | --card: oklch(0.205 0 0);
86 | --card-foreground: oklch(0.985 0 0);
87 | --popover: oklch(0.205 0 0);
88 | --popover-foreground: oklch(0.985 0 0);
89 | --primary: oklch(0.922 0 0);
90 | --primary-foreground: oklch(0.205 0 0);
91 | --secondary: oklch(0.269 0 0);
92 | --secondary-foreground: oklch(0.985 0 0);
93 | --muted: oklch(0.269 0 0);
94 | --muted-foreground: oklch(0.708 0 0);
95 | --accent: oklch(0.269 0 0);
96 | --accent-foreground: oklch(0.985 0 0);
97 | --destructive: oklch(0.704 0.191 22.216);
98 | --border: oklch(1 0 0 / 10%);
99 | --input: oklch(1 0 0 / 15%);
100 | --ring: oklch(0.556 0 0);
101 | --chart-1: oklch(0.488 0.243 264.376);
102 | --chart-2: oklch(0.696 0.17 162.48);
103 | --chart-3: oklch(0.769 0.188 70.08);
104 | --chart-4: oklch(0.627 0.265 303.9);
105 | --chart-5: oklch(0.645 0.246 16.439);
106 | --sidebar: oklch(0.205 0 0);
107 | --sidebar-foreground: oklch(0.985 0 0);
108 | --sidebar-primary: oklch(0.488 0.243 264.376);
109 | --sidebar-primary-foreground: oklch(0.985 0 0);
110 | --sidebar-accent: oklch(0.269 0 0);
111 | --sidebar-accent-foreground: oklch(0.985 0 0);
112 | --sidebar-border: oklch(1 0 0 / 10%);
113 | --sidebar-ring: oklch(0.556 0 0);
114 | }
115 |
116 | @layer base {
117 | * {
118 | @apply border-border outline-ring/50;
119 | }
120 | body {
121 | @apply bg-background text-foreground;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/apps/frontend/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Geist, Geist_Mono } from "next/font/google";
3 | import "./globals.css";
4 | import {
5 | ClerkProvider
6 | } from '@clerk/nextjs'
7 | import { Appbar } from "../components/Appbar";
8 | import { ThemeProvider } from "@/components/theme-provider";
9 |
10 | const geistSans = Geist({
11 | variable: "--font-geist-sans",
12 | subsets: ["latin"],
13 | });
14 |
15 | const geistMono = Geist_Mono({
16 | variable: "--font-geist-mono",
17 | subsets: ["latin"],
18 | });
19 |
20 | export const metadata: Metadata = {
21 | title: "Create Next App",
22 | description: "Generated by create next app",
23 | };
24 |
25 | export default function RootLayout({
26 | children,
27 | }: Readonly<{
28 | children: React.ReactNode;
29 | }>) {
30 | return (
31 |
32 |
33 |
36 |
37 |
38 | {children}
39 |
40 |
41 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/apps/frontend/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import React, { useEffect, useState } from 'react';
3 | import { Activity, Bell, Clock, Server, ArrowRight, Check, Moon, Sun } from 'lucide-react';
4 | import { useRouter } from 'next/navigation';
5 |
6 | function App() {
7 | const [darkMode, setDarkMode] = useState(false);
8 | const router = useRouter();
9 |
10 | useEffect(() => {
11 | if (darkMode) {
12 | document.documentElement.classList.add('dark');
13 | } else {
14 | document.documentElement.classList.remove('dark');
15 | }
16 | }, [darkMode]);
17 |
18 | return (
19 |
20 | {/* Hero Section */}
21 |
22 |
23 |
24 |
25 | Monitor Your Services with Confidence
26 |
27 |
28 | Get instant alerts when your services go down. Monitor uptime, performance, and ensure your business never misses a beat.
29 |
30 |
31 |
35 |
38 |
39 |
40 |
41 |

46 |
47 |
48 |
49 |
50 | {/* Features */}
51 |
52 |
53 |
54 | Everything you need for reliable monitoring
55 |
56 |
57 | }
59 | title="Instant Alerts"
60 | description="Get notified immediately when your services experience downtime through multiple channels."
61 | />
62 | }
64 | title="24/7 Monitoring"
65 | description="Round-the-clock monitoring from multiple locations worldwide."
66 | />
67 | }
69 | title="Detailed Reports"
70 | description="Comprehensive reports and analytics to track your service performance."
71 | />
72 |
73 |
74 |
75 |
76 | {/* Pricing */}
77 |
78 |
79 |
80 | Simple, transparent pricing
81 |
82 |
120 |
121 |
122 |
123 | {/* Footer */}
124 |
166 |
167 | );
168 | }
169 |
170 | function FeatureCard({ icon, title, description }) {
171 | return (
172 |
173 |
{icon}
174 |
{title}
175 |
{description}
176 |
177 | );
178 | }
179 |
180 | function PricingCard({ title, price, features, featured = false }) {
181 | return (
182 |
187 |
{title}
188 |
189 | ${price}
190 | /month
191 |
192 |
193 | {features.map((feature, index) => (
194 | -
195 |
196 | {feature}
197 |
198 | ))}
199 |
200 |
209 |
210 | );
211 | }
212 |
213 | export default App;
--------------------------------------------------------------------------------
/apps/frontend/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/apps/frontend/components/Appbar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | SignInButton,
5 | SignUpButton,
6 | SignedIn,
7 | SignedOut,
8 | UserButton,
9 | } from '@clerk/nextjs'
10 |
11 | export function Appbar() {
12 | return
13 |
DPin Uptime
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | }
--------------------------------------------------------------------------------
/apps/frontend/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ThemeProvider as NextThemesProvider } from "next-themes"
5 |
6 | export function ThemeProvider({
7 | children,
8 | ...props
9 | }: React.ComponentProps) {
10 | return {children}
11 | }
12 |
--------------------------------------------------------------------------------
/apps/frontend/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
16 | outline:
17 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20 | ghost:
21 | "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22 | link: "text-primary underline-offset-4 hover:underline",
23 | },
24 | size: {
25 | default: "h-9 px-4 py-2 has-[>svg]:px-3",
26 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28 | icon: "size-9",
29 | },
30 | },
31 | defaultVariants: {
32 | variant: "default",
33 | size: "default",
34 | },
35 | }
36 | )
37 |
38 | function Button({
39 | className,
40 | variant,
41 | size,
42 | asChild = false,
43 | ...props
44 | }: React.ComponentProps<"button"> &
45 | VariantProps & {
46 | asChild?: boolean
47 | }) {
48 | const Comp = asChild ? Slot : "button"
49 |
50 | return (
51 |
56 | )
57 | }
58 |
59 | export { Button, buttonVariants }
60 |
--------------------------------------------------------------------------------
/apps/frontend/config.ts:
--------------------------------------------------------------------------------
1 | export const API_BACKEND_URL = "http://localhost:8080";
--------------------------------------------------------------------------------
/apps/frontend/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname } from "path";
2 | import { fileURLToPath } from "url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname,
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends("next/core-web-vitals", "next/typescript"),
14 | ];
15 |
16 | export default eslintConfig;
17 |
--------------------------------------------------------------------------------
/apps/frontend/hooks/useWebsites.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { API_BACKEND_URL } from "@/config";
3 | import { useAuth } from "@clerk/nextjs";
4 | import axios from "axios";
5 | import { useEffect, useState } from "react";
6 |
7 | interface Website {
8 | id: string;
9 | url: string;
10 | ticks: {
11 | id: string;
12 | createdAt: string;
13 | status: string;
14 | latency: number;
15 | }[];
16 | }
17 |
18 | export function useWebsites() {
19 | const { getToken } = useAuth();
20 | const [websites, setWebsites] = useState([]);
21 |
22 | async function refreshWebsites() {
23 | const token = await getToken();
24 | const response = await axios.get(`${API_BACKEND_URL}/api/v1/websites`, {
25 | headers: {
26 | Authorization: token,
27 | },
28 | });
29 |
30 | setWebsites(response.data.websites);
31 | }
32 |
33 | useEffect(() => {
34 | refreshWebsites();
35 |
36 | const interval = setInterval(() => {
37 | refreshWebsites();
38 | }, 1000 * 60 * 1);
39 |
40 | return () => clearInterval(interval);
41 | }, []);
42 |
43 | return { websites, refreshWebsites };
44 |
45 | }
--------------------------------------------------------------------------------
/apps/frontend/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/apps/frontend/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next";
2 |
3 | const nextConfig: NextConfig = {
4 | /* config options here */
5 | };
6 |
7 | export default nextConfig;
8 |
--------------------------------------------------------------------------------
/apps/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@clerk/nextjs": "^6.12.5",
13 | "@radix-ui/react-slot": "^1.1.2",
14 | "axios": "^1.8.3",
15 | "class-variance-authority": "^0.7.1",
16 | "clsx": "^2.1.1",
17 | "lucide-react": "^0.479.0",
18 | "next": "15.2.2",
19 | "next-themes": "^0.4.6",
20 | "react": "^19.0.0",
21 | "react-dom": "^19.0.0",
22 | "tailwind-merge": "^3.0.2",
23 | "tailwindcss-animate": "^1.0.7"
24 | },
25 | "devDependencies": {
26 | "@eslint/eslintrc": "^3",
27 | "@tailwindcss/postcss": "^4",
28 | "@types/node": "^20",
29 | "@types/react": "^19",
30 | "@types/react-dom": "^19",
31 | "eslint": "^9",
32 | "eslint-config-next": "15.2.2",
33 | "tailwindcss": "^4",
34 | "typescript": "^5"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/apps/frontend/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: ["@tailwindcss/postcss"],
3 | };
4 |
5 | export default config;
6 |
--------------------------------------------------------------------------------
/apps/frontend/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/frontend/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/frontend/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/frontend/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/frontend/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/apps/hub/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | # Logs
4 |
5 | logs
6 | _.log
7 | npm-debug.log_
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 | .pnpm-debug.log*
12 |
13 | # Caches
14 |
15 | .cache
16 |
17 | # Diagnostic reports (https://nodejs.org/api/report.html)
18 |
19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
20 |
21 | # Runtime data
22 |
23 | pids
24 | _.pid
25 | _.seed
26 | *.pid.lock
27 |
28 | # Directory for instrumented libs generated by jscoverage/JSCover
29 |
30 | lib-cov
31 |
32 | # Coverage directory used by tools like istanbul
33 |
34 | coverage
35 | *.lcov
36 |
37 | # nyc test coverage
38 |
39 | .nyc_output
40 |
41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
42 |
43 | .grunt
44 |
45 | # Bower dependency directory (https://bower.io/)
46 |
47 | bower_components
48 |
49 | # node-waf configuration
50 |
51 | .lock-wscript
52 |
53 | # Compiled binary addons (https://nodejs.org/api/addons.html)
54 |
55 | build/Release
56 |
57 | # Dependency directories
58 |
59 | node_modules/
60 | jspm_packages/
61 |
62 | # Snowpack dependency directory (https://snowpack.dev/)
63 |
64 | web_modules/
65 |
66 | # TypeScript cache
67 |
68 | *.tsbuildinfo
69 |
70 | # Optional npm cache directory
71 |
72 | .npm
73 |
74 | # Optional eslint cache
75 |
76 | .eslintcache
77 |
78 | # Optional stylelint cache
79 |
80 | .stylelintcache
81 |
82 | # Microbundle cache
83 |
84 | .rpt2_cache/
85 | .rts2_cache_cjs/
86 | .rts2_cache_es/
87 | .rts2_cache_umd/
88 |
89 | # Optional REPL history
90 |
91 | .node_repl_history
92 |
93 | # Output of 'npm pack'
94 |
95 | *.tgz
96 |
97 | # Yarn Integrity file
98 |
99 | .yarn-integrity
100 |
101 | # dotenv environment variable files
102 |
103 | .env
104 | .env.development.local
105 | .env.test.local
106 | .env.production.local
107 | .env.local
108 |
109 | # parcel-bundler cache (https://parceljs.org/)
110 |
111 | .parcel-cache
112 |
113 | # Next.js build output
114 |
115 | .next
116 | out
117 |
118 | # Nuxt.js build / generate output
119 |
120 | .nuxt
121 | dist
122 |
123 | # Gatsby files
124 |
125 | # Comment in the public line in if your project uses Gatsby and not Next.js
126 |
127 | # https://nextjs.org/blog/next-9-1#public-directory-support
128 |
129 | # public
130 |
131 | # vuepress build output
132 |
133 | .vuepress/dist
134 |
135 | # vuepress v2.x temp and cache directory
136 |
137 | .temp
138 |
139 | # Docusaurus cache and generated files
140 |
141 | .docusaurus
142 |
143 | # Serverless directories
144 |
145 | .serverless/
146 |
147 | # FuseBox cache
148 |
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 |
153 | .dynamodb/
154 |
155 | # TernJS port file
156 |
157 | .tern-port
158 |
159 | # Stores VSCode versions used for testing VSCode extensions
160 |
161 | .vscode-test
162 |
163 | # yarn v2
164 |
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.*
170 |
171 | # IntelliJ based IDEs
172 | .idea
173 |
174 | # Finder (MacOS) folder config
175 | .DS_Store
176 |
--------------------------------------------------------------------------------
/apps/hub/README.md:
--------------------------------------------------------------------------------
1 | # hub
2 |
3 | To install dependencies:
4 |
5 | ```bash
6 | bun install
7 | ```
8 |
9 | To run:
10 |
11 | ```bash
12 | bun run index.ts
13 | ```
14 |
15 | This project was created using `bun init` in bun v1.2.2. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
16 |
--------------------------------------------------------------------------------
/apps/hub/index.ts:
--------------------------------------------------------------------------------
1 | import { randomUUIDv7, type ServerWebSocket } from "bun";
2 | import type { IncomingMessage, SignupIncomingMessage } from "common/types";
3 | import { prismaClient } from "db/client";
4 | import { PublicKey } from "@solana/web3.js";
5 | import nacl from "tweetnacl";
6 | import nacl_util from "tweetnacl-util";
7 |
8 | const availableValidators: { validatorId: string, socket: ServerWebSocket, publicKey: string }[] = [];
9 |
10 | const CALLBACKS : { [callbackId: string]: (data: IncomingMessage) => void } = {}
11 | const COST_PER_VALIDATION = 100; // in lamports
12 |
13 | Bun.serve({
14 | fetch(req, server) {
15 | if (server.upgrade(req)) {
16 | return;
17 | }
18 | return new Response("Upgrade failed", { status: 500 });
19 | },
20 | port: 8081,
21 | websocket: {
22 | async message(ws: ServerWebSocket, message: string) {
23 | const data: IncomingMessage = JSON.parse(message);
24 |
25 | if (data.type === 'signup') {
26 |
27 | const verified = await verifyMessage(
28 | `Signed message for ${data.data.callbackId}, ${data.data.publicKey}`,
29 | data.data.publicKey,
30 | data.data.signedMessage
31 | );
32 | if (verified) {
33 | await signupHandler(ws, data.data);
34 | }
35 | } else if (data.type === 'validate') {
36 | CALLBACKS[data.data.callbackId](data);
37 | delete CALLBACKS[data.data.callbackId];
38 | }
39 | },
40 | async close(ws: ServerWebSocket) {
41 | availableValidators.splice(availableValidators.findIndex(v => v.socket === ws), 1);
42 | }
43 | },
44 | });
45 |
46 | async function signupHandler(ws: ServerWebSocket, { ip, publicKey, signedMessage, callbackId }: SignupIncomingMessage) {
47 | const validatorDb = await prismaClient.validator.findFirst({
48 | where: {
49 | publicKey,
50 | },
51 | });
52 |
53 | if (validatorDb) {
54 | ws.send(JSON.stringify({
55 | type: 'signup',
56 | data: {
57 | validatorId: validatorDb.id,
58 | callbackId,
59 | },
60 | }));
61 |
62 | availableValidators.push({
63 | validatorId: validatorDb.id,
64 | socket: ws,
65 | publicKey: validatorDb.publicKey,
66 | });
67 | return;
68 | }
69 |
70 | //TODO: Given the ip, return the location
71 | const validator = await prismaClient.validator.create({
72 | data: {
73 | ip,
74 | publicKey,
75 | location: 'unknown',
76 | },
77 | });
78 |
79 | ws.send(JSON.stringify({
80 | type: 'signup',
81 | data: {
82 | validatorId: validator.id,
83 | callbackId,
84 | },
85 | }));
86 |
87 | availableValidators.push({
88 | validatorId: validator.id,
89 | socket: ws,
90 | publicKey: validator.publicKey,
91 | });
92 | }
93 |
94 | async function verifyMessage(message: string, publicKey: string, signature: string) {
95 | const messageBytes = nacl_util.decodeUTF8(message);
96 | const result = nacl.sign.detached.verify(
97 | messageBytes,
98 | new Uint8Array(JSON.parse(signature)),
99 | new PublicKey(publicKey).toBytes(),
100 | );
101 |
102 | return result;
103 | }
104 |
105 | setInterval(async () => {
106 | const websitesToMonitor = await prismaClient.website.findMany({
107 | where: {
108 | disabled: false,
109 | },
110 | });
111 |
112 | for (const website of websitesToMonitor) {
113 | availableValidators.forEach(validator => {
114 | const callbackId = randomUUIDv7();
115 | console.log(`Sending validate to ${validator.validatorId} ${website.url}`);
116 | validator.socket.send(JSON.stringify({
117 | type: 'validate',
118 | data: {
119 | url: website.url,
120 | callbackId
121 | },
122 | }));
123 |
124 | CALLBACKS[callbackId] = async (data: IncomingMessage) => {
125 | if (data.type === 'validate') {
126 | const { validatorId, status, latency, signedMessage } = data.data;
127 | const verified = await verifyMessage(
128 | `Replying to ${callbackId}`,
129 | validator.publicKey,
130 | signedMessage
131 | );
132 | if (!verified) {
133 | return;
134 | }
135 |
136 | await prismaClient.$transaction(async (tx) => {
137 | await tx.websiteTick.create({
138 | data: {
139 | websiteId: website.id,
140 | validatorId,
141 | status,
142 | latency,
143 | createdAt: new Date(),
144 | },
145 | });
146 |
147 | await tx.validator.update({
148 | where: { id: validatorId },
149 | data: {
150 | pendingPayouts: { increment: COST_PER_VALIDATION },
151 | },
152 | });
153 | });
154 | }
155 | };
156 | });
157 | }
158 | }, 60 * 1000);
--------------------------------------------------------------------------------
/apps/hub/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hub",
3 | "module": "index.ts",
4 | "type": "module",
5 | "devDependencies": {
6 | "@types/bun": "latest",
7 | "common": "*",
8 | "db": "*"
9 | },
10 | "peerDependencies": {
11 | "typescript": "^5.0.0"
12 | },
13 | "dependencies": {
14 | "@solana/kit": "^2.1.0",
15 | "@solana/web3.js": "^1.98.0",
16 | "tweetnacl": "^1.0.3"
17 | }
18 | }
--------------------------------------------------------------------------------
/apps/hub/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Enable latest features
4 | "lib": ["ESNext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "jsx": "react-jsx",
9 | "allowJs": true,
10 |
11 | // Bundler mode
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": true,
15 | "noEmit": true,
16 |
17 | // Best practices
18 | "strict": true,
19 | "skipLibCheck": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | // Some stricter flags (disabled by default)
23 | "noUnusedLocals": false,
24 | "noUnusedParameters": false,
25 | "noPropertyAccessFromIndexSignature": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apps/validator/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | # Logs
4 |
5 | logs
6 | _.log
7 | npm-debug.log_
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 | .pnpm-debug.log*
12 |
13 | # Caches
14 |
15 | .cache
16 |
17 | # Diagnostic reports (https://nodejs.org/api/report.html)
18 |
19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
20 |
21 | # Runtime data
22 |
23 | pids
24 | _.pid
25 | _.seed
26 | *.pid.lock
27 |
28 | # Directory for instrumented libs generated by jscoverage/JSCover
29 |
30 | lib-cov
31 |
32 | # Coverage directory used by tools like istanbul
33 |
34 | coverage
35 | *.lcov
36 |
37 | # nyc test coverage
38 |
39 | .nyc_output
40 |
41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
42 |
43 | .grunt
44 |
45 | # Bower dependency directory (https://bower.io/)
46 |
47 | bower_components
48 |
49 | # node-waf configuration
50 |
51 | .lock-wscript
52 |
53 | # Compiled binary addons (https://nodejs.org/api/addons.html)
54 |
55 | build/Release
56 |
57 | # Dependency directories
58 |
59 | node_modules/
60 | jspm_packages/
61 |
62 | # Snowpack dependency directory (https://snowpack.dev/)
63 |
64 | web_modules/
65 |
66 | # TypeScript cache
67 |
68 | *.tsbuildinfo
69 |
70 | # Optional npm cache directory
71 |
72 | .npm
73 |
74 | # Optional eslint cache
75 |
76 | .eslintcache
77 |
78 | # Optional stylelint cache
79 |
80 | .stylelintcache
81 |
82 | # Microbundle cache
83 |
84 | .rpt2_cache/
85 | .rts2_cache_cjs/
86 | .rts2_cache_es/
87 | .rts2_cache_umd/
88 |
89 | # Optional REPL history
90 |
91 | .node_repl_history
92 |
93 | # Output of 'npm pack'
94 |
95 | *.tgz
96 |
97 | # Yarn Integrity file
98 |
99 | .yarn-integrity
100 |
101 | # dotenv environment variable files
102 |
103 | .env
104 | .env.development.local
105 | .env.test.local
106 | .env.production.local
107 | .env.local
108 |
109 | # parcel-bundler cache (https://parceljs.org/)
110 |
111 | .parcel-cache
112 |
113 | # Next.js build output
114 |
115 | .next
116 | out
117 |
118 | # Nuxt.js build / generate output
119 |
120 | .nuxt
121 | dist
122 |
123 | # Gatsby files
124 |
125 | # Comment in the public line in if your project uses Gatsby and not Next.js
126 |
127 | # https://nextjs.org/blog/next-9-1#public-directory-support
128 |
129 | # public
130 |
131 | # vuepress build output
132 |
133 | .vuepress/dist
134 |
135 | # vuepress v2.x temp and cache directory
136 |
137 | .temp
138 |
139 | # Docusaurus cache and generated files
140 |
141 | .docusaurus
142 |
143 | # Serverless directories
144 |
145 | .serverless/
146 |
147 | # FuseBox cache
148 |
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 |
153 | .dynamodb/
154 |
155 | # TernJS port file
156 |
157 | .tern-port
158 |
159 | # Stores VSCode versions used for testing VSCode extensions
160 |
161 | .vscode-test
162 |
163 | # yarn v2
164 |
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.*
170 |
171 | # IntelliJ based IDEs
172 | .idea
173 |
174 | # Finder (MacOS) folder config
175 | .DS_Store
176 |
--------------------------------------------------------------------------------
/apps/validator/README.md:
--------------------------------------------------------------------------------
1 | # validator
2 |
3 | To install dependencies:
4 |
5 | ```bash
6 | bun install
7 | ```
8 |
9 | To run:
10 |
11 | ```bash
12 | bun run index.ts
13 | ```
14 |
15 | This project was created using `bun init` in bun v1.2.2. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
16 |
--------------------------------------------------------------------------------
/apps/validator/index.ts:
--------------------------------------------------------------------------------
1 | import { randomUUIDv7 } from "bun";
2 | import type { OutgoingMessage, SignupOutgoingMessage, ValidateOutgoingMessage } from "common/types";
3 | import { Keypair } from "@solana/web3.js";
4 | import nacl from "tweetnacl";
5 | import nacl_util from "tweetnacl-util";
6 |
7 | const CALLBACKS: {[callbackId: string]: (data: SignupOutgoingMessage) => void} = {}
8 |
9 | let validatorId: string | null = null;
10 |
11 | async function main() {
12 | const keypair = Keypair.fromSecretKey(
13 | Uint8Array.from(JSON.parse(process.env.PRIVATE_KEY!))
14 | );
15 | const ws = new WebSocket("ws://localhost:8081");
16 |
17 | ws.onmessage = async (event) => {
18 | const data: OutgoingMessage = JSON.parse(event.data);
19 | if (data.type === 'signup') {
20 | CALLBACKS[data.data.callbackId]?.(data.data)
21 | delete CALLBACKS[data.data.callbackId];
22 | } else if (data.type === 'validate') {
23 | await validateHandler(ws, data.data, keypair);
24 | }
25 | }
26 |
27 | ws.onopen = async () => {
28 | const callbackId = randomUUIDv7();
29 | CALLBACKS[callbackId] = (data: SignupOutgoingMessage) => {
30 | validatorId = data.validatorId;
31 | }
32 | const signedMessage = await signMessage(`Signed message for ${callbackId}, ${keypair.publicKey}`, keypair);
33 |
34 | ws.send(JSON.stringify({
35 | type: 'signup',
36 | data: {
37 | callbackId,
38 | ip: '127.0.0.1',
39 | publicKey: keypair.publicKey,
40 | signedMessage,
41 | },
42 | }));
43 | }
44 | }
45 |
46 | async function validateHandler(ws: WebSocket, { url, callbackId, websiteId }: ValidateOutgoingMessage, keypair: Keypair) {
47 | console.log(`Validating ${url}`);
48 | const startTime = Date.now();
49 | const signature = await signMessage(`Replying to ${callbackId}`, keypair);
50 |
51 | try {
52 | const response = await fetch(url);
53 | const endTime = Date.now();
54 | const latency = endTime - startTime;
55 | const status = response.status;
56 |
57 | console.log(url);
58 | console.log(status);
59 | ws.send(JSON.stringify({
60 | type: 'validate',
61 | data: {
62 | callbackId,
63 | status: status === 200 ? 'Good' : 'Bad',
64 | latency,
65 | websiteId,
66 | validatorId,
67 | signedMessage: signature,
68 | },
69 | }));
70 | } catch (error) {
71 | ws.send(JSON.stringify({
72 | type: 'validate',
73 | data: {
74 | callbackId,
75 | status:'Bad',
76 | latency: 1000,
77 | websiteId,
78 | validatorId,
79 | signedMessage: signature,
80 | },
81 | }));
82 | console.error(error);
83 | }
84 | }
85 |
86 | async function signMessage(message: string, keypair: Keypair) {
87 | const messageBytes = nacl_util.decodeUTF8(message);
88 | const signature = nacl.sign.detached(messageBytes, keypair.secretKey);
89 |
90 | return JSON.stringify(Array.from(signature));
91 | }
92 |
93 | main();
94 |
95 | setInterval(async () => {
96 |
97 | }, 10000);
--------------------------------------------------------------------------------
/apps/validator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "validator",
3 | "module": "index.ts",
4 | "type": "module",
5 | "devDependencies": {
6 | "@types/bun": "latest",
7 | "common": "*"
8 | },
9 | "peerDependencies": {
10 | "typescript": "^5.0.0"
11 | },
12 | "dependencies": {
13 | "@solana/kit": "^2.1.0",
14 | "@solana/web3.js": "^1.98.0",
15 | "tweetnacl": "^1.0.3",
16 | "tweetnacl-util": "^0.15.1"
17 | }
18 | }
--------------------------------------------------------------------------------
/apps/validator/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Enable latest features
4 | "lib": ["ESNext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "jsx": "react-jsx",
9 | "allowJs": true,
10 |
11 | // Bundler mode
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": true,
15 | "noEmit": true,
16 |
17 | // Best practices
18 | "strict": true,
19 | "skipLibCheck": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | // Some stricter flags (disabled by default)
23 | "noUnusedLocals": false,
24 | "noUnusedParameters": false,
25 | "noPropertyAccessFromIndexSignature": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dpin-uptime",
3 | "private": true,
4 | "scripts": {
5 | "build": "turbo run build",
6 | "dev": "turbo run dev",
7 | "lint": "turbo run lint",
8 | "format": "prettier --write \"**/*.{ts,tsx,md}\"",
9 | "check-types": "turbo run check-types"
10 | },
11 | "devDependencies": {
12 | "prettier": "^3.5.3",
13 | "turbo": "^2.4.4",
14 | "typescript": "5.8.2"
15 | },
16 | "engines": {
17 | "node": ">=18"
18 | },
19 | "packageManager": "bun@1.2.2",
20 | "workspaces": [
21 | "apps/*",
22 | "packages/*"
23 | ],
24 | "dependencies": {
25 | "prisma": "^6.5.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/common/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | # Logs
4 |
5 | logs
6 | _.log
7 | npm-debug.log_
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 | .pnpm-debug.log*
12 |
13 | # Caches
14 |
15 | .cache
16 |
17 | # Diagnostic reports (https://nodejs.org/api/report.html)
18 |
19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
20 |
21 | # Runtime data
22 |
23 | pids
24 | _.pid
25 | _.seed
26 | *.pid.lock
27 |
28 | # Directory for instrumented libs generated by jscoverage/JSCover
29 |
30 | lib-cov
31 |
32 | # Coverage directory used by tools like istanbul
33 |
34 | coverage
35 | *.lcov
36 |
37 | # nyc test coverage
38 |
39 | .nyc_output
40 |
41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
42 |
43 | .grunt
44 |
45 | # Bower dependency directory (https://bower.io/)
46 |
47 | bower_components
48 |
49 | # node-waf configuration
50 |
51 | .lock-wscript
52 |
53 | # Compiled binary addons (https://nodejs.org/api/addons.html)
54 |
55 | build/Release
56 |
57 | # Dependency directories
58 |
59 | node_modules/
60 | jspm_packages/
61 |
62 | # Snowpack dependency directory (https://snowpack.dev/)
63 |
64 | web_modules/
65 |
66 | # TypeScript cache
67 |
68 | *.tsbuildinfo
69 |
70 | # Optional npm cache directory
71 |
72 | .npm
73 |
74 | # Optional eslint cache
75 |
76 | .eslintcache
77 |
78 | # Optional stylelint cache
79 |
80 | .stylelintcache
81 |
82 | # Microbundle cache
83 |
84 | .rpt2_cache/
85 | .rts2_cache_cjs/
86 | .rts2_cache_es/
87 | .rts2_cache_umd/
88 |
89 | # Optional REPL history
90 |
91 | .node_repl_history
92 |
93 | # Output of 'npm pack'
94 |
95 | *.tgz
96 |
97 | # Yarn Integrity file
98 |
99 | .yarn-integrity
100 |
101 | # dotenv environment variable files
102 |
103 | .env
104 | .env.development.local
105 | .env.test.local
106 | .env.production.local
107 | .env.local
108 |
109 | # parcel-bundler cache (https://parceljs.org/)
110 |
111 | .parcel-cache
112 |
113 | # Next.js build output
114 |
115 | .next
116 | out
117 |
118 | # Nuxt.js build / generate output
119 |
120 | .nuxt
121 | dist
122 |
123 | # Gatsby files
124 |
125 | # Comment in the public line in if your project uses Gatsby and not Next.js
126 |
127 | # https://nextjs.org/blog/next-9-1#public-directory-support
128 |
129 | # public
130 |
131 | # vuepress build output
132 |
133 | .vuepress/dist
134 |
135 | # vuepress v2.x temp and cache directory
136 |
137 | .temp
138 |
139 | # Docusaurus cache and generated files
140 |
141 | .docusaurus
142 |
143 | # Serverless directories
144 |
145 | .serverless/
146 |
147 | # FuseBox cache
148 |
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 |
153 | .dynamodb/
154 |
155 | # TernJS port file
156 |
157 | .tern-port
158 |
159 | # Stores VSCode versions used for testing VSCode extensions
160 |
161 | .vscode-test
162 |
163 | # yarn v2
164 |
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.*
170 |
171 | # IntelliJ based IDEs
172 | .idea
173 |
174 | # Finder (MacOS) folder config
175 | .DS_Store
176 |
--------------------------------------------------------------------------------
/packages/common/README.md:
--------------------------------------------------------------------------------
1 | # common
2 |
3 | To install dependencies:
4 |
5 | ```bash
6 | bun install
7 | ```
8 |
9 | To run:
10 |
11 | ```bash
12 | bun run index.ts
13 | ```
14 |
15 | This project was created using `bun init` in bun v1.2.2. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
16 |
--------------------------------------------------------------------------------
/packages/common/index.ts:
--------------------------------------------------------------------------------
1 | export interface SignupIncomingMessage {
2 | ip: string;
3 | publicKey: string;
4 | signedMessage: string;
5 | callbackId: string;
6 | }
7 |
8 | export interface ValidateIncomingMessage {
9 | callbackId: string;
10 | signedMessage: string;
11 | status: 'Good' | 'Bad';
12 | latency: number;
13 | websiteId: string;
14 | validatorId: string;
15 | }
16 |
17 | export interface SignupOutgoingMessage {
18 | validatorId: string;
19 | callbackId: string;
20 | }
21 |
22 | export interface ValidateOutgoingMessage {
23 | url: string,
24 | callbackId: string,
25 | websiteId: string;
26 | }
27 |
28 | export type IncomingMessage = {
29 | type: 'signup'
30 | data: SignupIncomingMessage
31 | } | {
32 | type: 'validate'
33 | data: ValidateIncomingMessage
34 | }
35 |
36 | export type OutgoingMessage = {
37 | type: 'signup'
38 | data: SignupOutgoingMessage
39 | } | {
40 | type: 'validate'
41 | data: ValidateOutgoingMessage
42 | }
43 |
--------------------------------------------------------------------------------
/packages/common/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "common",
3 | "module": "index.ts",
4 | "type": "module",
5 | "exports": {
6 | "./types": "./index.ts"
7 | },
8 | "devDependencies": {
9 | "@types/bun": "latest"
10 | },
11 | "peerDependencies": {
12 | "typescript": "^5.0.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/packages/common/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Enable latest features
4 | "lib": ["ESNext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "jsx": "react-jsx",
9 | "allowJs": true,
10 |
11 | // Bundler mode
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": true,
15 | "noEmit": true,
16 |
17 | // Best practices
18 | "strict": true,
19 | "skipLibCheck": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | // Some stricter flags (disabled by default)
23 | "noUnusedLocals": false,
24 | "noUnusedParameters": false,
25 | "noPropertyAccessFromIndexSignature": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/db/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | # Logs
4 |
5 | logs
6 | _.log
7 | npm-debug.log_
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 | .pnpm-debug.log*
12 |
13 | # Caches
14 |
15 | .cache
16 |
17 | # Diagnostic reports (https://nodejs.org/api/report.html)
18 |
19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
20 |
21 | # Runtime data
22 |
23 | pids
24 | _.pid
25 | _.seed
26 | *.pid.lock
27 |
28 | # Directory for instrumented libs generated by jscoverage/JSCover
29 |
30 | lib-cov
31 |
32 | # Coverage directory used by tools like istanbul
33 |
34 | coverage
35 | *.lcov
36 |
37 | # nyc test coverage
38 |
39 | .nyc_output
40 |
41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
42 |
43 | .grunt
44 |
45 | # Bower dependency directory (https://bower.io/)
46 |
47 | bower_components
48 |
49 | # node-waf configuration
50 |
51 | .lock-wscript
52 |
53 | # Compiled binary addons (https://nodejs.org/api/addons.html)
54 |
55 | build/Release
56 |
57 | # Dependency directories
58 |
59 | node_modules/
60 | jspm_packages/
61 |
62 | # Snowpack dependency directory (https://snowpack.dev/)
63 |
64 | web_modules/
65 |
66 | # TypeScript cache
67 |
68 | *.tsbuildinfo
69 |
70 | # Optional npm cache directory
71 |
72 | .npm
73 |
74 | # Optional eslint cache
75 |
76 | .eslintcache
77 |
78 | # Optional stylelint cache
79 |
80 | .stylelintcache
81 |
82 | # Microbundle cache
83 |
84 | .rpt2_cache/
85 | .rts2_cache_cjs/
86 | .rts2_cache_es/
87 | .rts2_cache_umd/
88 |
89 | # Optional REPL history
90 |
91 | .node_repl_history
92 |
93 | # Output of 'npm pack'
94 |
95 | *.tgz
96 |
97 | # Yarn Integrity file
98 |
99 | .yarn-integrity
100 |
101 | # dotenv environment variable files
102 |
103 | .env
104 | .env.development.local
105 | .env.test.local
106 | .env.production.local
107 | .env.local
108 |
109 | # parcel-bundler cache (https://parceljs.org/)
110 |
111 | .parcel-cache
112 |
113 | # Next.js build output
114 |
115 | .next
116 | out
117 |
118 | # Nuxt.js build / generate output
119 |
120 | .nuxt
121 | dist
122 |
123 | # Gatsby files
124 |
125 | # Comment in the public line in if your project uses Gatsby and not Next.js
126 |
127 | # https://nextjs.org/blog/next-9-1#public-directory-support
128 |
129 | # public
130 |
131 | # vuepress build output
132 |
133 | .vuepress/dist
134 |
135 | # vuepress v2.x temp and cache directory
136 |
137 | .temp
138 |
139 | # Docusaurus cache and generated files
140 |
141 | .docusaurus
142 |
143 | # Serverless directories
144 |
145 | .serverless/
146 |
147 | # FuseBox cache
148 |
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 |
153 | .dynamodb/
154 |
155 | # TernJS port file
156 |
157 | .tern-port
158 |
159 | # Stores VSCode versions used for testing VSCode extensions
160 |
161 | .vscode-test
162 |
163 | # yarn v2
164 |
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.*
170 |
171 | # IntelliJ based IDEs
172 | .idea
173 |
174 | # Finder (MacOS) folder config
175 | .DS_Store
176 |
--------------------------------------------------------------------------------
/packages/db/README.md:
--------------------------------------------------------------------------------
1 | # db
2 |
3 | To install dependencies:
4 |
5 | ```bash
6 | bun install
7 | ```
8 |
9 | To run:
10 |
11 | ```bash
12 | bun run index.ts
13 | ```
14 |
15 | This project was created using `bun init` in bun v1.2.2. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
16 |
--------------------------------------------------------------------------------
/packages/db/index.ts:
--------------------------------------------------------------------------------
1 | console.log("Hello via Bun!");
--------------------------------------------------------------------------------
/packages/db/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "db",
3 | "module": "index.ts",
4 | "type": "module",
5 | "exports": {
6 | "./client": "./src/index.ts"
7 | },
8 | "devDependencies": {
9 | "@types/bun": "latest"
10 | },
11 | "peerDependencies": {
12 | "typescript": "^5.0.0"
13 | },
14 | "dependencies": {
15 | "prisma": "^6.5.0"
16 | },
17 | "prisma": {
18 | "seed": "./prisma/seed.ts"
19 | }
20 | }
--------------------------------------------------------------------------------
/packages/db/prisma/migrations/20250314011543_init/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateEnum
2 | CREATE TYPE "WebsiteStatus" AS ENUM ('Good', 'Bad');
3 |
4 | -- CreateTable
5 | CREATE TABLE "User" (
6 | "id" TEXT NOT NULL,
7 | "email" TEXT NOT NULL,
8 |
9 | CONSTRAINT "User_pkey" PRIMARY KEY ("id")
10 | );
11 |
12 | -- CreateTable
13 | CREATE TABLE "Website" (
14 | "id" TEXT NOT NULL,
15 | "url" TEXT NOT NULL,
16 | "userId" TEXT NOT NULL,
17 |
18 | CONSTRAINT "Website_pkey" PRIMARY KEY ("id")
19 | );
20 |
21 | -- CreateTable
22 | CREATE TABLE "Validator" (
23 | "id" TEXT NOT NULL,
24 | "publicKey" TEXT NOT NULL,
25 | "location" TEXT NOT NULL,
26 | "ip" TEXT NOT NULL,
27 |
28 | CONSTRAINT "Validator_pkey" PRIMARY KEY ("id")
29 | );
30 |
31 | -- CreateTable
32 | CREATE TABLE "WebsiteTick" (
33 | "id" TEXT NOT NULL,
34 | "websiteId" TEXT NOT NULL,
35 | "validatorId" TEXT NOT NULL,
36 | "createdAt" TIMESTAMP(3) NOT NULL,
37 | "status" "WebsiteStatus" NOT NULL,
38 | "latency" DOUBLE PRECISION NOT NULL,
39 |
40 | CONSTRAINT "WebsiteTick_pkey" PRIMARY KEY ("id")
41 | );
42 |
43 | -- AddForeignKey
44 | ALTER TABLE "WebsiteTick" ADD CONSTRAINT "WebsiteTick_websiteId_fkey" FOREIGN KEY ("websiteId") REFERENCES "Website"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
45 |
46 | -- AddForeignKey
47 | ALTER TABLE "WebsiteTick" ADD CONSTRAINT "WebsiteTick_validatorId_fkey" FOREIGN KEY ("validatorId") REFERENCES "Validator"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
48 |
--------------------------------------------------------------------------------
/packages/db/prisma/migrations/20250314012859_added_disabled/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Website" ADD COLUMN "disabled" BOOLEAN NOT NULL DEFAULT false;
3 |
--------------------------------------------------------------------------------
/packages/db/prisma/migrations/20250315042613_added_payout/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "User" ADD COLUMN "pendingPayouts" INTEGER NOT NULL DEFAULT 0;
3 |
--------------------------------------------------------------------------------
/packages/db/prisma/migrations/20250315042714_fix_payout/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the column `pendingPayouts` on the `User` table. All the data in the column will be lost.
5 |
6 | */
7 | -- AlterTable
8 | ALTER TABLE "User" DROP COLUMN "pendingPayouts";
9 |
10 | -- AlterTable
11 | ALTER TABLE "Validator" ADD COLUMN "pendingPayouts" INTEGER NOT NULL DEFAULT 0;
12 |
--------------------------------------------------------------------------------
/packages/db/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (e.g., Git)
3 | provider = "postgresql"
--------------------------------------------------------------------------------
/packages/db/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
5 | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
6 |
7 | generator client {
8 | provider = "prisma-client-js"
9 | }
10 |
11 | datasource db {
12 | provider = "postgresql"
13 | url = env("DATABASE_URL")
14 | }
15 |
16 | model User {
17 | id String @id @default(uuid())
18 | email String
19 | }
20 |
21 | model Website {
22 | id String @id @default(uuid())
23 | url String
24 | userId String
25 | ticks WebsiteTick[]
26 | disabled Boolean @default(false)
27 | }
28 |
29 | model Validator {
30 | id String @id @default(uuid())
31 | publicKey String
32 | location String
33 | ip String
34 | pendingPayouts Int @default(0)
35 | ticks WebsiteTick[]
36 | }
37 |
38 | model WebsiteTick {
39 | id String @id @default(uuid())
40 | websiteId String
41 | validatorId String
42 | createdAt DateTime
43 | status WebsiteStatus
44 | latency Float
45 | website Website @relation(fields: [websiteId], references: [id])
46 | validator Validator @relation(fields: [validatorId], references: [id])
47 | }
48 |
49 | enum WebsiteStatus {
50 | Good
51 | Bad
52 | }
--------------------------------------------------------------------------------
/packages/db/prisma/seed.ts:
--------------------------------------------------------------------------------
1 |
2 | import { prismaClient } from "../src";
3 |
4 | const USER_ID = "4";
5 |
6 | async function seed() {
7 | await prismaClient.user.create({
8 | data: {
9 | id: USER_ID,
10 | email: "test@test.com",
11 | }
12 | })
13 |
14 | const website = await prismaClient.website.create({
15 | data: {
16 | url: "https://test.com",
17 | userId: USER_ID
18 | }
19 | })
20 |
21 | const validator = await prismaClient.validator.create({
22 | data: {
23 | publicKey: "0x12341223123",
24 | location: "Delhi",
25 | ip: "127.0.0.1",
26 | }
27 | })
28 |
29 | await prismaClient.websiteTick.create({
30 | data: {
31 | websiteId: website.id,
32 | status: "Good",
33 | createdAt: new Date(),
34 | latency: 100,
35 | validatorId: validator.id
36 | }
37 | })
38 |
39 | await prismaClient.websiteTick.create({
40 | data: {
41 | websiteId: website.id,
42 | status: "Good",
43 | createdAt: new Date(Date.now() - 1000 * 60 *10),
44 | latency: 100,
45 | validatorId: validator.id
46 | }
47 | })
48 |
49 | await prismaClient.websiteTick.create({
50 | data: {
51 | websiteId: website.id,
52 | status: "Bad",
53 | createdAt: new Date(Date.now() - 1000 * 60 * 20),
54 | latency: 100,
55 | validatorId: validator.id
56 | }
57 | })
58 | }
59 |
60 | seed();
--------------------------------------------------------------------------------
/packages/db/src/index.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client"
2 |
3 | export const prismaClient = new PrismaClient()
4 |
5 |
6 |
--------------------------------------------------------------------------------
/packages/db/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Enable latest features
4 | "lib": ["ESNext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "jsx": "react-jsx",
9 | "allowJs": true,
10 |
11 | // Bundler mode
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": true,
15 | "noEmit": true,
16 |
17 | // Best practices
18 | "strict": true,
19 | "skipLibCheck": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | // Some stricter flags (disabled by default)
23 | "noUnusedLocals": false,
24 | "noUnusedParameters": false,
25 | "noPropertyAccessFromIndexSignature": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/eslint-config/README.md:
--------------------------------------------------------------------------------
1 | # `@turbo/eslint-config`
2 |
3 | Collection of internal eslint configurations.
4 |
--------------------------------------------------------------------------------
/packages/eslint-config/base.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import eslintConfigPrettier from "eslint-config-prettier";
3 | import turboPlugin from "eslint-plugin-turbo";
4 | import tseslint from "typescript-eslint";
5 | import onlyWarn from "eslint-plugin-only-warn";
6 |
7 | /**
8 | * A shared ESLint configuration for the repository.
9 | *
10 | * @type {import("eslint").Linter.Config[]}
11 | * */
12 | export const config = [
13 | js.configs.recommended,
14 | eslintConfigPrettier,
15 | ...tseslint.configs.recommended,
16 | {
17 | plugins: {
18 | turbo: turboPlugin,
19 | },
20 | rules: {
21 | "turbo/no-undeclared-env-vars": "warn",
22 | },
23 | },
24 | {
25 | plugins: {
26 | onlyWarn,
27 | },
28 | },
29 | {
30 | ignores: ["dist/**"],
31 | },
32 | ];
33 |
--------------------------------------------------------------------------------
/packages/eslint-config/next.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import eslintConfigPrettier from "eslint-config-prettier";
3 | import tseslint from "typescript-eslint";
4 | import pluginReactHooks from "eslint-plugin-react-hooks";
5 | import pluginReact from "eslint-plugin-react";
6 | import globals from "globals";
7 | import pluginNext from "@next/eslint-plugin-next";
8 | import { config as baseConfig } from "./base.js";
9 |
10 | /**
11 | * A custom ESLint configuration for libraries that use Next.js.
12 | *
13 | * @type {import("eslint").Linter.Config[]}
14 | * */
15 | export const nextJsConfig = [
16 | ...baseConfig,
17 | js.configs.recommended,
18 | eslintConfigPrettier,
19 | ...tseslint.configs.recommended,
20 | {
21 | ...pluginReact.configs.flat.recommended,
22 | languageOptions: {
23 | ...pluginReact.configs.flat.recommended.languageOptions,
24 | globals: {
25 | ...globals.serviceworker,
26 | },
27 | },
28 | },
29 | {
30 | plugins: {
31 | "@next/next": pluginNext,
32 | },
33 | rules: {
34 | ...pluginNext.configs.recommended.rules,
35 | ...pluginNext.configs["core-web-vitals"].rules,
36 | },
37 | },
38 | {
39 | plugins: {
40 | "react-hooks": pluginReactHooks,
41 | },
42 | settings: { react: { version: "detect" } },
43 | rules: {
44 | ...pluginReactHooks.configs.recommended.rules,
45 | // React scope no longer necessary with new JSX transform.
46 | "react/react-in-jsx-scope": "off",
47 | },
48 | },
49 | ];
50 |
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/eslint-config",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "private": true,
6 | "exports": {
7 | "./base": "./base.js",
8 | "./next-js": "./next.js",
9 | "./react-internal": "./react-internal.js"
10 | },
11 | "devDependencies": {
12 | "@eslint/js": "^9.22.0",
13 | "@next/eslint-plugin-next": "^15.2.1",
14 | "eslint": "^9.22.0",
15 | "eslint-config-prettier": "^10.1.1",
16 | "eslint-plugin-only-warn": "^1.1.0",
17 | "eslint-plugin-react": "^7.37.4",
18 | "eslint-plugin-react-hooks": "^5.2.0",
19 | "eslint-plugin-turbo": "^2.4.4",
20 | "globals": "^16.0.0",
21 | "typescript": "^5.8.2",
22 | "typescript-eslint": "^8.26.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/eslint-config/react-internal.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import eslintConfigPrettier from "eslint-config-prettier";
3 | import tseslint from "typescript-eslint";
4 | import pluginReactHooks from "eslint-plugin-react-hooks";
5 | import pluginReact from "eslint-plugin-react";
6 | import globals from "globals";
7 | import { config as baseConfig } from "./base.js";
8 |
9 | /**
10 | * A custom ESLint configuration for libraries that use React.
11 | *
12 | * @type {import("eslint").Linter.Config[]} */
13 | export const config = [
14 | ...baseConfig,
15 | js.configs.recommended,
16 | eslintConfigPrettier,
17 | ...tseslint.configs.recommended,
18 | pluginReact.configs.flat.recommended,
19 | {
20 | languageOptions: {
21 | ...pluginReact.configs.flat.recommended.languageOptions,
22 | globals: {
23 | ...globals.serviceworker,
24 | ...globals.browser,
25 | },
26 | },
27 | },
28 | {
29 | plugins: {
30 | "react-hooks": pluginReactHooks,
31 | },
32 | settings: { react: { version: "detect" } },
33 | rules: {
34 | ...pluginReactHooks.configs.recommended.rules,
35 | // React scope no longer necessary with new JSX transform.
36 | "react/react-in-jsx-scope": "off",
37 | },
38 | },
39 | ];
40 |
--------------------------------------------------------------------------------
/packages/typescript-config/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "declaration": true,
5 | "declarationMap": true,
6 | "esModuleInterop": true,
7 | "incremental": false,
8 | "isolatedModules": true,
9 | "lib": ["es2022", "DOM", "DOM.Iterable"],
10 | "module": "NodeNext",
11 | "moduleDetection": "force",
12 | "moduleResolution": "NodeNext",
13 | "noUncheckedIndexedAccess": true,
14 | "resolveJsonModule": true,
15 | "skipLibCheck": true,
16 | "strict": true,
17 | "target": "ES2022"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/typescript-config/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "./base.json",
4 | "compilerOptions": {
5 | "plugins": [{ "name": "next" }],
6 | "module": "ESNext",
7 | "moduleResolution": "Bundler",
8 | "allowJs": true,
9 | "jsx": "preserve",
10 | "noEmit": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/typescript-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/typescript-config",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "publishConfig": {
7 | "access": "public"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/typescript-config/react-library.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "./base.json",
4 | "compilerOptions": {
5 | "jsx": "react-jsx"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/ui/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@repo/eslint-config/react-internal";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/packages/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/ui",
3 | "version": "0.0.0",
4 | "private": true,
5 | "exports": {
6 | "./*": "./src/*.tsx"
7 | },
8 | "scripts": {
9 | "lint": "eslint . --max-warnings 0",
10 | "generate:component": "turbo gen react-component",
11 | "check-types": "tsc --noEmit"
12 | },
13 | "devDependencies": {
14 | "@repo/eslint-config": "*",
15 | "@repo/typescript-config": "*",
16 | "@turbo/gen": "^2.4.4",
17 | "@types/node": "^22.13.9",
18 | "@types/react": "19.0.10",
19 | "@types/react-dom": "19.0.4",
20 | "eslint": "^9.22.0",
21 | "typescript": "5.8.2"
22 | },
23 | "dependencies": {
24 | "react": "^19.0.0",
25 | "react-dom": "^19.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/ui/src/button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ReactNode } from "react";
4 |
5 | interface ButtonProps {
6 | children: ReactNode;
7 | className?: string;
8 | appName: string;
9 | }
10 |
11 | export const Button = ({ children, className, appName }: ButtonProps) => {
12 | return (
13 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/packages/ui/src/card.tsx:
--------------------------------------------------------------------------------
1 | import { type JSX } from "react";
2 |
3 | export function Card({
4 | className,
5 | title,
6 | children,
7 | href,
8 | }: {
9 | className?: string;
10 | title: string;
11 | children: React.ReactNode;
12 | href: string;
13 | }): JSX.Element {
14 | return (
15 |
21 |
22 | {title} ->
23 |
24 | {children}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/packages/ui/src/code.tsx:
--------------------------------------------------------------------------------
1 | import { type JSX } from "react";
2 |
3 | export function Code({
4 | children,
5 | className,
6 | }: {
7 | children: React.ReactNode;
8 | className?: string;
9 | }): JSX.Element {
10 | return {children}
;
11 | }
12 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/ui/turbo/generators/config.ts:
--------------------------------------------------------------------------------
1 | import type { PlopTypes } from "@turbo/gen";
2 |
3 | // Learn more about Turborepo Generators at https://turbo.build/repo/docs/core-concepts/monorepos/code-generation
4 |
5 | export default function generator(plop: PlopTypes.NodePlopAPI): void {
6 | // A simple generator to add a new React component to the internal UI library
7 | plop.setGenerator("react-component", {
8 | description: "Adds a new react component",
9 | prompts: [
10 | {
11 | type: "input",
12 | name: "name",
13 | message: "What is the name of the component?",
14 | },
15 | ],
16 | actions: [
17 | {
18 | type: "add",
19 | path: "src/{{kebabCase name}}.tsx",
20 | templateFile: "templates/component.hbs",
21 | },
22 | {
23 | type: "append",
24 | path: "package.json",
25 | pattern: /"exports": {(?)/g,
26 | template: ' "./{{kebabCase name}}": "./src/{{kebabCase name}}.tsx",',
27 | },
28 | ],
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/packages/ui/turbo/generators/templates/component.hbs:
--------------------------------------------------------------------------------
1 | export const {{ pascalCase name }} = ({ children }: { children: React.ReactNode }) => {
2 | return (
3 |
4 |
{{ pascalCase name }} Component
5 | {children}
6 |
7 | );
8 | };
9 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "tui",
4 | "tasks": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "inputs": ["$TURBO_DEFAULT$", ".env*"],
8 | "outputs": [".next/**", "!.next/cache/**"]
9 | },
10 | "lint": {
11 | "dependsOn": ["^lint"]
12 | },
13 | "check-types": {
14 | "dependsOn": ["^check-types"]
15 | },
16 | "dev": {
17 | "cache": false,
18 | "persistent": true
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------