diff --git a/.gitignore b/.gitignore index 485282a..45a32ef 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ next-env.d.ts /playwright/.cache/ .cache -.db \ No newline at end of file +.db +.turbo \ No newline at end of file diff --git a/.env.example b/backend/.env.example similarity index 100% rename from .env.example rename to backend/.env.example diff --git a/.prettierrc b/backend/.prettierrc similarity index 100% rename from .prettierrc rename to backend/.prettierrc diff --git a/CONTRIBUTING.md b/backend/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to backend/CONTRIBUTING.md diff --git a/LICENSE b/backend/LICENSE similarity index 100% rename from LICENSE rename to backend/LICENSE diff --git a/README.md b/backend/README.md similarity index 100% rename from README.md rename to backend/README.md diff --git a/backend/bun.lockb b/backend/bun.lockb new file mode 100755 index 0000000..aa9bef5 Binary files /dev/null and b/backend/bun.lockb differ diff --git a/backend/next.txt b/backend/next.txt new file mode 100644 index 0000000..482f60c --- /dev/null +++ b/backend/next.txt @@ -0,0 +1,7 @@ +* Increase time +* Write README +* Test with a new submission and approval +* Test with reject +* Integrate NEAR contract +* Build a small dashboard to see what there is to be approved and rejected? +* Tag all of the admins in the "Received" reply diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..24185a5 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,47 @@ +{ + "name": "backend", + "version": "0.0.1", + "packageManager": "bun@1.0.27", + "scripts": { + "build": "bun build ./src/index.ts --outdir=dist", + "start": "bun run dist/index.js", + "dev": "bun run --watch src/index.ts", + "test": "bun test", + "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", + "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@types/express": "^4.17.17", + "@types/jest": "^29.5.11", + "@types/node": "^20.10.6", + "@types/ora": "^3.2.0", + "jest": "^29.7.0", + "prettier": "^3.3.3", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "typescript": "^5.3.3" + }, + "dependencies": { + "agent-twitter-client": "^0.0.16", + "cors": "^2.8.5", + "@types/cors": "^2.8.17", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "near-api-js": "^2.1.4", + "ora": "^8.1.1", + "winston": "^3.17.0", + "winston-console-format": "^1.0.8" + } +} diff --git a/src/config/admins.ts b/backend/src/config/admins.ts similarity index 100% rename from src/config/admins.ts rename to backend/src/config/admins.ts diff --git a/src/config/config.ts b/backend/src/config/config.ts similarity index 100% rename from src/config/config.ts rename to backend/src/config/config.ts diff --git a/src/index.ts b/backend/src/index.ts similarity index 57% rename from src/index.ts rename to backend/src/index.ts index a0bd62c..99582f7 100644 --- a/src/index.ts +++ b/backend/src/index.ts @@ -1,6 +1,10 @@ import dotenv from "dotenv"; +import express from "express"; +import cors from "cors"; +import path from "path"; import { TwitterService } from "./services/twitter/client"; import { NearService } from "./services/near"; +import { db } from "./services/db"; import config from "./config/config"; import { logger, @@ -10,6 +14,52 @@ import { cleanup } from "./utils/logger"; +// Initialize Express +const app = express(); +const PORT = process.env.PORT || 3000; + +// Middleware +app.use(cors()); +app.use(express.json()); + +// Serve static frontend files in production +if (process.env.NODE_ENV === 'production') { + app.use(express.static(path.join(__dirname, '../frontend/dist'))); +} + +// API Routes +app.get('/api/submissions', (req, res) => { + try { + const status = req.query.status as "pending" | "approved" | "rejected"; + const submissions = status ? + db.getSubmissionsByStatus(status) : + db.getAllSubmissions(); + res.json(submissions); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch submissions' }); + } +}); + +app.get('/api/submissions/:tweetId', (req, res) => { + try { + const submission = db.getSubmission(req.params.tweetId); + if (!submission) { + res.status(404).json({ error: 'Submission not found' }); + return; + } + res.json(submission); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch submission' }); + } +}); + +// Serve frontend for all other routes in production +if (process.env.NODE_ENV === 'production') { + app.get('*', (req, res) => { + res.sendFile(path.join(__dirname, '../frontend/dist/index.html')); + }); +} + async function main() { try { // Load environment variables @@ -28,6 +78,12 @@ async function main() { await twitterService.initialize(); succeedSpinner('twitter-init', 'Twitter service initialized'); + // Start Express server + startSpinner('express', 'Starting Express server...'); + app.listen(PORT, () => { + succeedSpinner('express', `Express server running on port ${PORT}`); + }); + // Handle graceful shutdown process.on("SIGINT", async () => { startSpinner('shutdown', 'Shutting down gracefully...'); @@ -54,7 +110,7 @@ async function main() { } catch (error) { // Handle any initialization errors - ['env', 'near', 'twitter-init', 'twitter-mentions'].forEach(key => { + ['env', 'near', 'twitter-init', 'twitter-mentions', 'express'].forEach(key => { failSpinner(key, `Failed during ${key}`); }); logger.error('Startup', error); diff --git a/src/services/db/index.ts b/backend/src/services/db/index.ts similarity index 100% rename from src/services/db/index.ts rename to backend/src/services/db/index.ts diff --git a/src/services/near/index.ts b/backend/src/services/near/index.ts similarity index 100% rename from src/services/near/index.ts rename to backend/src/services/near/index.ts diff --git a/src/services/twitter/client.ts b/backend/src/services/twitter/client.ts similarity index 83% rename from src/services/twitter/client.ts rename to backend/src/services/twitter/client.ts index f3726c1..f69422f 100644 --- a/src/services/twitter/client.ts +++ b/backend/src/services/twitter/client.ts @@ -209,43 +209,62 @@ export class TwitterService { const userId = tweet.userId; if (!userId || !tweet.id) return; - // Get submission count from database instead of memory - const dailyCount = db.getDailySubmissionCount(userId); - - if (dailyCount >= this.DAILY_SUBMISSION_LIMIT) { - await this.replyToTweet( - tweet.id, - "You've reached your daily submission limit. Please try again tomorrow." - ); - logger.info(`User ${userId} has reached limit, replied to submission.`); + // Get the tweet being replied to + const inReplyToId = tweet.inReplyToStatusId; + if (!inReplyToId) { + logger.error(`Submission tweet ${tweet.id} is not a reply to another tweet`); return; } - const submission: TwitterSubmission = { - tweetId: tweet.id, - userId: userId, - content: tweet.text || "", - hashtags: tweet.hashtags || [], - status: "pending", - moderationHistory: [], - }; + try { + // Fetch the original tweet that's being submitted + const originalTweet = await this.client.getTweet(inReplyToId); + if (!originalTweet) { + logger.error(`Could not fetch original tweet ${inReplyToId}`); + return; + } - // Save submission to database - db.saveSubmission(submission); - // Increment submission count in database - db.incrementDailySubmissionCount(userId); + // Get submission count from database + const dailyCount = db.getDailySubmissionCount(userId); - // Send acknowledgment and save its ID - const acknowledgmentTweetId = await this.replyToTweet( - tweet.id, - "Successfully submitted to publicgoods.news!" - ); - - if (acknowledgmentTweetId) { - db.updateSubmissionAcknowledgment(tweet.id, acknowledgmentTweetId); - logger.info(`Successfully submitted. Sent reply: ${this.getTweetLink(acknowledgmentTweetId)}`) - } else { - logger.error(`Failed to acknowledge submission: ${this.getTweetLink(tweet.id, tweet.username)}`) + if (dailyCount >= this.DAILY_SUBMISSION_LIMIT) { + await this.replyToTweet( + tweet.id, + "You've reached your daily submission limit. Please try again tomorrow." + ); + logger.info(`User ${userId} has reached limit, replied to submission.`); + return; + } + + // Create submission using the original tweet's content + const submission: TwitterSubmission = { + tweetId: originalTweet.id!, // The tweet being submitted + userId: userId, // The user who submitted it + content: originalTweet.text || "", + hashtags: originalTweet.hashtags || [], + status: "pending", + moderationHistory: [], + }; + + // Save submission to database + db.saveSubmission(submission); + // Increment submission count in database + db.incrementDailySubmissionCount(userId); + + // Send acknowledgment and save its ID + const acknowledgmentTweetId = await this.replyToTweet( + tweet.id, // Reply to the submission tweet + "Successfully submitted to publicgoods.news!" + ); + + if (acknowledgmentTweetId) { + db.updateSubmissionAcknowledgment(originalTweet.id!, acknowledgmentTweetId); + logger.info(`Successfully submitted. Sent reply: ${this.getTweetLink(acknowledgmentTweetId)}`) + } else { + logger.error(`Failed to acknowledge submission: ${this.getTweetLink(tweet.id, tweet.username)}`) + } + } catch (error) { + logger.error(`Error handling submission for tweet ${tweet.id}:`, error); } } diff --git a/src/types/bun.d.ts b/backend/src/types/bun.d.ts similarity index 100% rename from src/types/bun.d.ts rename to backend/src/types/bun.d.ts diff --git a/src/types/index.ts b/backend/src/types/index.ts similarity index 100% rename from src/types/index.ts rename to backend/src/types/index.ts diff --git a/src/types/near.ts b/backend/src/types/near.ts similarity index 100% rename from src/types/near.ts rename to backend/src/types/near.ts diff --git a/src/types/twitter.ts b/backend/src/types/twitter.ts similarity index 100% rename from src/types/twitter.ts rename to backend/src/types/twitter.ts diff --git a/src/utils/cache.ts b/backend/src/utils/cache.ts similarity index 100% rename from src/utils/cache.ts rename to backend/src/utils/cache.ts diff --git a/src/utils/logger.ts b/backend/src/utils/logger.ts similarity index 100% rename from src/utils/logger.ts rename to backend/src/utils/logger.ts diff --git a/tsconfig.json b/backend/tsconfig.json similarity index 100% rename from tsconfig.json rename to backend/tsconfig.json diff --git a/bun.lockb b/bun.lockb index 9694d22..7225c14 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..74872fd --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/frontend/bun.lockb b/frontend/bun.lockb new file mode 100755 index 0000000..218578d Binary files /dev/null and b/frontend/bun.lockb differ diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..dd74230 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + Curation Dashboard + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..aca8ed4 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,35 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port 5173", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/typography": "^0.5.15", + "autoprefixer": "^10.4.20", + "axios": "^1.7.9", + "postcss": "^8.4.49", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.0.2", + "tailwindcss": "^3.4.16" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.15.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.12.0", + "typescript": "~5.6.2", + "typescript-eslint": "^8.15.0", + "vite": "^6.0.1" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..4304b65 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,18 @@ +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import axios from 'axios'; +import SubmissionList from './components/SubmissionList'; + +// Configure axios base URL for API requests +axios.defaults.baseURL = 'http://localhost:3000'; + +function App() { + return ( + + + } /> + + + ); +} + +export default App; diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/SubmissionList.tsx b/frontend/src/components/SubmissionList.tsx new file mode 100644 index 0000000..ee878a0 --- /dev/null +++ b/frontend/src/components/SubmissionList.tsx @@ -0,0 +1,140 @@ +import { useEffect, useState } from 'react'; +import axios from 'axios'; +import { TwitterSubmission } from '../types/twitter'; + +const StatusBadge = ({ status }: { status: TwitterSubmission['status'] }) => { + const className = `status-badge status-${status}`; + return {status}; +}; + +const SubmissionList = () => { + const [submissions, setSubmissions] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [filter, setFilter] = useState('all'); + + const fetchSubmissions = async () => { + try { + setLoading(true); + const url = filter === 'all' + ? '/api/submissions' + : `/api/submissions?status=${filter}`; + const response = await axios.get(url); + setSubmissions(response.data); + setError(null); + } catch (err) { + setError('Failed to fetch submissions'); + console.error('Error fetching submissions:', err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchSubmissions(); + // Set up auto-refresh every 30 seconds + const interval = setInterval(fetchSubmissions, 30000); + return () => clearInterval(interval); + }, [filter]); + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+

{error}

+ +
+ ); + } + + return ( +
+
+

Public Goods News Submissions

+
+ {(['all', 'pending', 'approved', 'rejected'] as const).map((status) => ( + + ))} +
+
+ +
+ {submissions.map((submission) => ( +
+
+
+

Tweet ID: {submission.tweetId}

+

{submission.content}

+
+ {submission.hashtags.map((tag) => ( + #{tag} + ))} +
+
+ +
+ + {submission.category && ( +

+ Category: {submission.category} +

+ )} + + {submission.description && ( +

+ Description: {submission.description} +

+ )} + + {submission.moderationHistory.length > 0 && ( +
+

Moderation History

+
+ {submission.moderationHistory.map((history, index) => ( +
+ {history.action} by {history.adminId} on{' '} + {new Date(history.timestamp).toLocaleString()} +
+ ))} +
+
+ )} +
+ ))} + + {submissions.length === 0 && ( +
+ No submissions found +
+ )} +
+
+ ); +}; + +export default SubmissionList; diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..a6fa24f --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,21 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer components { + .status-badge { + @apply px-2 py-1 rounded-full text-sm font-semibold; + } + + .status-pending { + @apply bg-yellow-100 text-yellow-800; + } + + .status-approved { + @apply bg-green-100 text-green-800; + } + + .status-rejected { + @apply bg-red-100 text-red-800; + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..964aeb4 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/frontend/src/types/twitter.ts b/frontend/src/types/twitter.ts new file mode 100644 index 0000000..4cc9b4a --- /dev/null +++ b/frontend/src/types/twitter.ts @@ -0,0 +1,19 @@ +export interface TwitterSubmission { + tweetId: string; + userId: string; + content: string; + hashtags: Array; + category?: string; + description?: string; + status: "pending" | "approved" | "rejected"; + moderationHistory: Moderation[]; + acknowledgmentTweetId?: string; + moderationResponseTweetId?: string; +} + +export interface Moderation { + adminId: string; + action: "approve" | "reject"; + timestamp: Date; + tweetId: string; +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..e9baf90 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [ + require('@tailwindcss/typography'), + ], +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..358ca9b --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/package.json b/package.json index e77295b..3fdc96d 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,18 @@ { "name": "public-goods-news", - "version": "0.0.1", - "homepage": "/", + "private": true, + "type": "module", + "packageManager": "bun@1.0.27", "scripts": { - "build": "bun build ./src/index.ts --outdir=dist", - "start": "bun run dist/index.js", - "dev": "bun run --watch src/index.ts", - "test": "bun test", - "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", - "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "dev": "bunx turbo run dev", + "build": "bunx turbo run build", + "lint": "bunx turbo run lint" }, + "workspaces": [ + "frontend", + "backend" + ], "devDependencies": { - "@types/express": "^4.17.17", - "@types/jest": "^29.5.11", - "@types/node": "^20.10.6", - "@types/ora": "^3.2.0", - "jest": "^29.7.0", - "prettier": "^3.3.3", - "ts-jest": "^29.1.1", - "ts-node": "^10.9.1", - "typescript": "^5.3.3" - }, - "dependencies": { - "agent-twitter-client": "^0.0.16", - "dotenv": "^16.0.3", - "express": "^4.18.2", - "near-api-js": "^2.1.4", - "ora": "^8.1.1", - "winston": "^3.17.0", - "winston-console-format": "^1.0.8" + "turbo": "latest" } } diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..e818cf5 --- /dev/null +++ b/turbo.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["**/.env"], + "globalEnv": ["NODE_ENV"], + "tasks": { + "dev": { + "cache": false, + "persistent": true + }, + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "lint": { + "outputs": [] + } + } +}