initial
This commit is contained in:
commit
a2bc2fdeda
21 changed files with 3343 additions and 0 deletions
1
backend/.env
Normal file
1
backend/.env
Normal file
|
@ -0,0 +1 @@
|
|||
DATABASE_URL=sqlite://test.db
|
2
backend/.gitignore
vendored
Normal file
2
backend/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
test.db*
|
2652
backend/Cargo.lock
generated
Normal file
2652
backend/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
15
backend/Cargo.toml
Normal file
15
backend/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.93"
|
||||
axum = "0.7.9"
|
||||
futures = "0.3.31"
|
||||
reqwest = { version = "0.12.9", features = ["json", "stream"] }
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
sqlx = { version = "0.8.2", features = ["json", "runtime-tokio", "sqlite"] }
|
||||
tokio = { version = "1.41.1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.12", features = ["codec", "io"] }
|
2
backend/migrations/20241130210848_initial.down.sql
Normal file
2
backend/migrations/20241130210848_initial.down.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE blocks;
|
||||
DROP TABLE connections;
|
24
backend/migrations/20241130210848_initial.up.sql
Normal file
24
backend/migrations/20241130210848_initial.up.sql
Normal file
|
@ -0,0 +1,24 @@
|
|||
-- Add up migration script here
|
||||
|
||||
CREATE TABLE blocks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
kind TEXT NOT NULL,
|
||||
options TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE connections (
|
||||
fromId INTEGER NOT NULL,
|
||||
fromKey TEXT NOT NULL,
|
||||
toId INTEGER NOT NULL,
|
||||
toKey TEXT NOT NULL,
|
||||
FOREIGN KEY (fromId) REFERENCES blocks(id),
|
||||
FOREIGN KEY (toId) REFERENCES blocks(id)
|
||||
);
|
||||
|
||||
CREATE TABLE runs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
blockId INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
info TEXT NOT NULL,
|
||||
FOREIGN KEY (blockId) REFERENCES blocks(id)
|
||||
);
|
2
backend/rustfmt.toml
Normal file
2
backend/rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
tab_spaces = 2
|
||||
max_width = 80
|
82
backend/src/block.rs
Normal file
82
backend/src/block.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
use axum::{
|
||||
extract::{Path, State},
|
||||
Json,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use sqlx::{prelude::FromRow, types::Json as SqlxJson, Row};
|
||||
|
||||
use crate::{error::AppResult, AppState};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
||||
pub struct Block {
|
||||
pub id: i64,
|
||||
pub kind: String,
|
||||
pub options: SqlxJson<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BlockOptions {
|
||||
#[serde(default)]
|
||||
inputs: Vec<BlockInput>,
|
||||
|
||||
#[serde(flatten)]
|
||||
extra_options: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BlockInput {
|
||||
name: String,
|
||||
datatype: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn list_blocks(
|
||||
state: State<AppState>,
|
||||
) -> AppResult<Json<Vec<Block>>> {
|
||||
let results = sqlx::query_as::<_, Block>(r"SELECT * FROM blocks")
|
||||
.fetch_all(&state.db)
|
||||
.await?;
|
||||
|
||||
Ok(Json(results))
|
||||
}
|
||||
|
||||
pub async fn create_block(state: State<AppState>) -> AppResult<Json<i64>> {
|
||||
let id = sqlx::query(
|
||||
r#"INSERT INTO blocks (kind, options) VALUES (?, ?) RETURNING id"#,
|
||||
)
|
||||
.bind("helloge")
|
||||
.bind(json!({}))
|
||||
.fetch_one(&state.db)
|
||||
.await?;
|
||||
|
||||
let id: i64 = id.get(0);
|
||||
|
||||
Ok(Json(id))
|
||||
}
|
||||
|
||||
pub async fn get_block(
|
||||
state: State<AppState>,
|
||||
Path(id): Path<i64>,
|
||||
) -> AppResult<Json<Block>> {
|
||||
let result = sqlx::query_as::<_, Block>(r"SELECT * FROM blocks WHERE id = ?")
|
||||
.bind(id)
|
||||
.fetch_one(&state.db)
|
||||
.await?;
|
||||
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn update_block() -> AppResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_block(
|
||||
state: State<AppState>,
|
||||
Path(id): Path<i64>,
|
||||
) -> AppResult {
|
||||
sqlx::query(r"DELETE FROM blocks WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(&state.db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
31
backend/src/error.rs
Normal file
31
backend/src/error.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
|
||||
pub type AppResult<T = (), E = AppError> = std::result::Result<T, E>;
|
||||
|
||||
// Make our own error that wraps `anyhow::Error`.
|
||||
pub struct AppError(anyhow::Error);
|
||||
|
||||
// Tell axum how to convert `AppError` into a response.
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", self.0),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
|
||||
// `Result<_, AppError>`. That way you don't need to do that manually.
|
||||
impl<E> From<E> for AppError
|
||||
where
|
||||
E: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
Self(err.into())
|
||||
}
|
||||
}
|
83
backend/src/main.rs
Normal file
83
backend/src/main.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
mod block;
|
||||
mod error;
|
||||
mod worker;
|
||||
|
||||
use anyhow::Result;
|
||||
use axum::{
|
||||
routing::{delete, get, post},
|
||||
Router,
|
||||
};
|
||||
use sqlx::{
|
||||
migrate,
|
||||
sqlite::{SqliteConnectOptions, SqlitePoolOptions, UpdateHookResult},
|
||||
SqlitePool,
|
||||
};
|
||||
use tokio::{
|
||||
net::TcpListener,
|
||||
sync::mpsc::{self},
|
||||
};
|
||||
|
||||
use crate::block::{
|
||||
create_block, delete_block, get_block, list_blocks, update_block,
|
||||
};
|
||||
use crate::worker::worker;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub db: SqlitePool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseUpdate {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let (tx, database_updates) = mpsc::unbounded_channel();
|
||||
|
||||
let db = {
|
||||
let options = SqliteConnectOptions::new()
|
||||
.filename("test.db")
|
||||
.create_if_missing(true);
|
||||
|
||||
SqlitePoolOptions::new()
|
||||
.after_connect(move |c, md| {
|
||||
let tx = tx.clone();
|
||||
Box::pin(async {
|
||||
let mut c = c.lock_handle().await?;
|
||||
c.set_update_hook(move |u: UpdateHookResult<'_>| {
|
||||
if u.table == "blocks" {
|
||||
tx.send(DatabaseUpdate {}).unwrap();
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
.connect_with(options)
|
||||
.await?
|
||||
};
|
||||
|
||||
migrate!().run(&db).await?;
|
||||
|
||||
let state = AppState { db };
|
||||
|
||||
tokio::spawn(worker(state.clone(), database_updates));
|
||||
|
||||
let app = Router::new()
|
||||
// Block CRUD
|
||||
.route("/block", get(list_blocks))
|
||||
.route("/block", post(create_block))
|
||||
.route("/block/:id", get(get_block))
|
||||
.route("/block/:id", post(update_block))
|
||||
.route("/block/:id", delete(delete_block))
|
||||
.with_state(state);
|
||||
|
||||
let listener = TcpListener::bind("0.0.0.0:3000").await?;
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
71
backend/src/worker.rs
Normal file
71
backend/src/worker.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::TryStreamExt;
|
||||
use serde_json::Value;
|
||||
use tokio::{
|
||||
sync::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
task::JoinHandle,
|
||||
};
|
||||
use tokio_util::{
|
||||
codec::{FramedRead, LinesCodec},
|
||||
io::StreamReader,
|
||||
};
|
||||
|
||||
use crate::{block::Block, AppState, DatabaseUpdate};
|
||||
|
||||
pub async fn worker(
|
||||
state: AppState,
|
||||
mut database_updates: UnboundedReceiver<DatabaseUpdate>,
|
||||
) {
|
||||
let mut prev_handle: Option<JoinHandle<()>> = None;
|
||||
|
||||
loop {
|
||||
let upd = database_updates.recv().await;
|
||||
println!("Update: {upd:?}");
|
||||
|
||||
if let Some(handle) = prev_handle.take() {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
let state = state.clone();
|
||||
prev_handle = Some(tokio::spawn(async move {
|
||||
run(state).await;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(state: AppState) -> Result<()> {
|
||||
let blocks = sqlx::query_as::<_, Block>(r"SELECT * FROM blocks")
|
||||
.fetch_all(&state.db)
|
||||
.await?;
|
||||
|
||||
println!("blocks: {blocks:?}");
|
||||
|
||||
for block in blocks {
|
||||
if block.kind == "ntfy" {
|
||||
tokio::spawn(run_ntfy_listener());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_ntfy_listener() {
|
||||
// TODO: Exponential backoff retry
|
||||
let res = reqwest::get("https://ntfy.sh/LIXE61yAwoClnxyL/json")
|
||||
.await
|
||||
.unwrap();
|
||||
let stream = res.bytes_stream().map_err(std::io::Error::other);
|
||||
let stream = StreamReader::new(stream);
|
||||
let mut stream = FramedRead::new(stream, LinesCodec::new());
|
||||
|
||||
while let Ok(Some(line)) = stream.try_next().await {
|
||||
let line = Value::from_str(&line).unwrap();
|
||||
println!("line: {line:?}");
|
||||
|
||||
// TODO: Push this event to the database
|
||||
// TODO: Get a list of all of the connections from the database
|
||||
// TODO: Push a new task for each of the new connections into the database
|
||||
}
|
||||
}
|
175
frontend/.gitignore
vendored
Normal file
175
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,175 @@
|
|||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
15
frontend/README.md
Normal file
15
frontend/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frontend
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.1.29. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
BIN
frontend/bun.lockb
Normal file
BIN
frontend/bun.lockb
Normal file
Binary file not shown.
7
frontend/index.html
Normal file
7
frontend/index.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
43
frontend/package.json
Normal file
43
frontend/package.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/cookie": "^1.0.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"vite": "^6.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/carousel": "^7.14.3",
|
||||
"@mantine/charts": "^7.14.3",
|
||||
"@mantine/code-highlight": "^7.14.3",
|
||||
"@mantine/core": "^7.14.3",
|
||||
"@mantine/dates": "^7.14.3",
|
||||
"@mantine/dropzone": "^7.14.3",
|
||||
"@mantine/form": "^7.14.3",
|
||||
"@mantine/hooks": "^7.14.3",
|
||||
"@mantine/modals": "^7.14.3",
|
||||
"@mantine/notifications": "^7.14.3",
|
||||
"@mantine/nprogress": "^7.14.3",
|
||||
"@mantine/spotlight": "^7.14.3",
|
||||
"@mantine/tiptap": "^7.14.3",
|
||||
"@tanstack/react-query": "^5.62.0",
|
||||
"@tiptap/extension-link": "^2.10.3",
|
||||
"@tiptap/pm": "^2.10.3",
|
||||
"@tiptap/react": "^2.10.3",
|
||||
"@tiptap/starter-kit": "^2.10.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"embla-carousel-react": "^7.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"recharts": "2"
|
||||
}
|
||||
}
|
14
frontend/postcss.config.cjs
Normal file
14
frontend/postcss.config.cjs
Normal file
|
@ -0,0 +1,14 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-simple-vars': {
|
||||
variables: {
|
||||
'mantine-breakpoint-xs': '36em',
|
||||
'mantine-breakpoint-sm': '48em',
|
||||
'mantine-breakpoint-md': '62em',
|
||||
'mantine-breakpoint-lg': '75em',
|
||||
'mantine-breakpoint-xl': '88em',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
74
frontend/src/App.tsx
Normal file
74
frontend/src/App.tsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import {
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import "@mantine/core/styles.css";
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function Block({ block }) {
|
||||
const qc = useQueryClient();
|
||||
const { mutate: deleteBlock } = useMutation({
|
||||
mutationFn: async () => {
|
||||
await fetch(`/api/block/${block.id}`, { method: "DELETE" });
|
||||
},
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["block/list"] });
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<li>
|
||||
{JSON.stringify(block)} (
|
||||
<button onClick={() => deleteBlock()}>delete</button>)
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function BlockList() {
|
||||
const { data: blocks, status } = useQuery({
|
||||
queryKey: ["block/list"],
|
||||
queryFn: async () => (await fetch("/api/block")).json(),
|
||||
});
|
||||
|
||||
if (status !== "success") return <>{status}</>;
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{blocks.map((block) => (
|
||||
<Block key={block.id} block={block} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateButton() {
|
||||
const qc = useQueryClient();
|
||||
const { mutate: createBlock } = useMutation({
|
||||
mutationFn: async () => {
|
||||
await fetch(`/api/block`, { method: "POST" });
|
||||
},
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["block/list"] });
|
||||
},
|
||||
});
|
||||
|
||||
return <button onClick={() => createBlock()}>create</button>;
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<MantineProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BlockList />
|
||||
|
||||
<CreateButton />
|
||||
</QueryClientProvider>
|
||||
</MantineProvider>
|
||||
);
|
||||
}
|
10
frontend/src/index.tsx
Normal file
10
frontend/src/index.tsx
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
|
||||
const root = document.getElementById("root");
|
||||
|
||||
if (!root) {
|
||||
throw new Error("could not create root");
|
||||
}
|
||||
|
||||
createRoot(root).render(<App />);
|
28
frontend/tsconfig.json
Normal file
28
frontend/tsconfig.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
|
||||
}
|
||||
}
|
12
frontend/vite.config.ts
Normal file
12
frontend/vite.config.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:3000",
|
||||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
Loading…
Add table
Reference in a new issue