feat: add backend
This commit is contained in:
5
apps/server/.gitignore
vendored
Normal file
5
apps/server/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
|
||||
/generated/prisma
|
||||
10
apps/server/app.ts
Normal file
10
apps/server/app.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import Koa from 'koa';
|
||||
import { registerMiddlewares } from './middlewares/index.ts';
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
registerMiddlewares(app);
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('server started at http://localhost:3000');
|
||||
});
|
||||
11
apps/server/controllers/application.ts
Normal file
11
apps/server/controllers/application.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { Context } from 'koa';
|
||||
import prisma from '../libs/db.ts';
|
||||
|
||||
export async function list(ctx: Context) {
|
||||
const list = await prisma.application.findMany({
|
||||
where: {
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
ctx.body = list;
|
||||
}
|
||||
7
apps/server/libs/db.ts
Normal file
7
apps/server/libs/db.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { PrismaClient } from '../generated/prisma/index.js'
|
||||
|
||||
const prismaClientSingleton = () => {
|
||||
return new PrismaClient();
|
||||
};
|
||||
|
||||
export default prismaClientSingleton();
|
||||
10
apps/server/middlewares/index.ts
Normal file
10
apps/server/middlewares/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Router } from './router.ts';
|
||||
import { ResponseTime } from './responseTime.ts';
|
||||
import type Koa from 'koa';
|
||||
|
||||
export function registerMiddlewares(app: Koa) {
|
||||
const router = new Router();
|
||||
const responseTime = new ResponseTime();
|
||||
responseTime.apply(app);
|
||||
router.apply(app);
|
||||
}
|
||||
13
apps/server/middlewares/responseTime.ts
Normal file
13
apps/server/middlewares/responseTime.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Middleware } from './types.ts';
|
||||
import type Koa from 'koa';
|
||||
|
||||
export class ResponseTime implements Middleware {
|
||||
apply(app: Koa): void {
|
||||
app.use(async (ctx, next) => {
|
||||
const start = Date.now();
|
||||
await next();
|
||||
const ms = Date.now() - start;
|
||||
ctx.set('X-Response-Time', `${ms}ms`);
|
||||
});
|
||||
}
|
||||
}
|
||||
19
apps/server/middlewares/router.ts
Normal file
19
apps/server/middlewares/router.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import KoaRouter from '@koa/router';
|
||||
import type Koa from 'koa';
|
||||
import type { Middleware } from './types.ts';
|
||||
import * as application from '../controllers/application.ts';
|
||||
|
||||
export class Router implements Middleware {
|
||||
private router: KoaRouter;
|
||||
constructor() {
|
||||
this.router = new KoaRouter({
|
||||
prefix: '/api',
|
||||
});
|
||||
this.router.get('/application/list', application.list);
|
||||
}
|
||||
|
||||
apply(app: Koa) {
|
||||
app.use(this.router.routes());
|
||||
app.use(this.router.allowedMethods());
|
||||
}
|
||||
}
|
||||
5
apps/server/middlewares/types.ts
Normal file
5
apps/server/middlewares/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type Koa from 'koa';
|
||||
|
||||
export abstract class Middleware {
|
||||
abstract apply(app: Koa, options?: unknown): void;
|
||||
}
|
||||
27
apps/server/package.json
Normal file
27
apps/server/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dev": "tsx watch ./app.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"type": "module",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@koa/router": "^14.0.0",
|
||||
"@prisma/client": "^6.15.0",
|
||||
"koa": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node-ts": "^23.6.1",
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/koa": "^3.0.0",
|
||||
"@types/koa__router": "^12.0.4",
|
||||
"@types/node": "^24.3.0",
|
||||
"prisma": "^6.15.0",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
BIN
apps/server/prisma/data/dev.db
Normal file
BIN
apps/server/prisma/data/dev.db
Normal file
Binary file not shown.
35
apps/server/prisma/schema.prisma
Normal file
35
apps/server/prisma/schema.prisma
Normal file
@@ -0,0 +1,35 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../generated/prisma"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Application {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
description String?
|
||||
repository String
|
||||
valid Int @default(1)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdBy String
|
||||
updatedBy String
|
||||
}
|
||||
|
||||
model Environment {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
description String?
|
||||
valid Int @default(1)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdBy String
|
||||
updatedBy String
|
||||
}
|
||||
6
apps/server/tsconfig.json
Normal file
6
apps/server/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": [
|
||||
"@tsconfig/node22/tsconfig.json",
|
||||
"@tsconfig/node-ts/tsconfig.json"
|
||||
]
|
||||
}
|
||||
36
apps/web/README.md
Normal file
36
apps/web/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Rsbuild project
|
||||
|
||||
## Setup
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Get started
|
||||
|
||||
Start the dev server, and the app will be available at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Build the app for production:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
Preview the production build locally:
|
||||
|
||||
```bash
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
## Learn more
|
||||
|
||||
To learn more about Rsbuild, check out the following resources:
|
||||
|
||||
- [Rsbuild documentation](https://rsbuild.rs) - explore Rsbuild features and APIs.
|
||||
- [Rsbuild GitHub repository](https://github.com/web-infra-dev/rsbuild) - your feedback and contributions are welcome!
|
||||
32
apps/web/package.json
Normal file
32
apps/web/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "rsbuild build",
|
||||
"check": "biome check --write",
|
||||
"dev": "rsbuild dev --open",
|
||||
"format": "biome format --write",
|
||||
"preview": "rsbuild preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@arco-design/web-react": "^2.66.4",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router": "^7.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arco-plugins/unplugin-react": "2.0.0-beta.5",
|
||||
"@rsbuild/core": "^1.4.13",
|
||||
"@rsbuild/plugin-less": "^1.4.0",
|
||||
"@rsbuild/plugin-react": "^1.3.4",
|
||||
"@rsbuild/plugin-svgr": "^1.2.2",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/react": "^19.1.9",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.2+sha512.93e57b0126f0df74ce6bff29680394c0ba54ec47246b9cf321f0121d8d9bb03f750a705f24edc3c1180853afd7c2c3b94196d0a3d53d3e069d9e2793ef11f321"
|
||||
}
|
||||
5
apps/web/postcss.config.mjs
Normal file
5
apps/web/postcss.config.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
};
|
||||
18
apps/web/rsbuild.config.ts
Normal file
18
apps/web/rsbuild.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ArcoDesignPlugin } from "@arco-plugins/unplugin-react";
|
||||
import { defineConfig } from "@rsbuild/core";
|
||||
import { pluginReact } from "@rsbuild/plugin-react";
|
||||
import { pluginLess } from "@rsbuild/plugin-less";
|
||||
import { pluginSvgr } from '@rsbuild/plugin-svgr';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [pluginReact(), pluginLess(), pluginSvgr()],
|
||||
tools: {
|
||||
rspack: {
|
||||
plugins: [
|
||||
new ArcoDesignPlugin({
|
||||
defaultLanguage: "zh-CN",
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
9
apps/web/src/assets/images/logo.svg
Normal file
9
apps/web/src/assets/images/logo.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="33" height="34" viewBox="0 0 33 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.8125 17.2932C32.8125 19.6071 32.3208 21.8488 31.4779 23.8735L25.6476 17.944C26.35 16.787 26.7012 15.4131 26.7012 13.9669C26.7715 9.84522 23.47 6.44662 19.3958 6.44662C17.9207 6.44662 16.586 6.88048 15.4621 7.60359L10.0533 2.10799C12.0904 1.16795 14.2679 0.661774 16.6562 0.661774C25.5773 0.661774 32.8125 8.10976 32.8125 17.2932ZM7.80543 3.40958L13.6357 9.41135C12.6523 10.7129 12.0904 12.3038 12.0904 14.0392C12.0904 18.2332 15.3918 21.6318 19.466 21.6318C21.1519 21.6318 22.6973 21.0534 23.9617 20.041L30.2134 26.4767C27.2632 30.9599 22.3461 33.9246 16.6562 33.9246C7.73519 33.9246 0.5 26.4767 0.5 17.2932C0.5 11.436 3.38003 6.37431 7.80543 3.40958ZM19.466 18.7394C22.2056 18.7394 24.3832 16.4978 24.3832 13.6777C24.3832 10.8576 22.2056 8.61594 19.466 8.61594C16.7265 8.61594 14.5489 10.8576 14.5489 13.6777C14.5489 16.4978 16.7265 18.7394 19.466 18.7394Z" fill="url(#paint0_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="6.88952" y1="1.73016" x2="27.8689" y2="33.2773" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#73DFE7"/>
|
||||
<stop offset="1" stop-color="#0095F7"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
11
apps/web/src/env.d.ts
vendored
Normal file
11
apps/web/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/// <reference types="@rsbuild/core/types" />
|
||||
|
||||
/**
|
||||
* Imports the SVG file as a React component.
|
||||
* @requires [@rsbuild/plugin-svgr](https://npmjs.com/package/@rsbuild/plugin-svgr)
|
||||
*/
|
||||
declare module '*.svg?react' {
|
||||
import type React from 'react';
|
||||
const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
|
||||
export default ReactComponent;
|
||||
}
|
||||
17
apps/web/src/index.tsx
Normal file
17
apps/web/src/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from '@pages/App';
|
||||
import { BrowserRouter } from 'react-router';
|
||||
|
||||
const rootEl = document.getElementById('root');
|
||||
|
||||
if (rootEl) {
|
||||
const root = ReactDOM.createRoot(rootEl);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
}
|
||||
18
apps/web/src/pages/App.tsx
Normal file
18
apps/web/src/pages/App.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Route, Routes } from 'react-router';
|
||||
import Home from '@pages/home';
|
||||
import Login from '@pages/login';
|
||||
import Application from '@pages/application';
|
||||
|
||||
import '@styles/index.css';
|
||||
const App = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />}>
|
||||
<Route path="application" element={<Application />} index />
|
||||
</Route>
|
||||
<Route path="/login" element={<Login />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
12
apps/web/src/pages/application/index.tsx
Normal file
12
apps/web/src/pages/application/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useState } from "react";
|
||||
|
||||
function Application() {
|
||||
const [apps, setApps] = useState<Application[]>([]);
|
||||
return (
|
||||
<div>
|
||||
application
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Application;
|
||||
15
apps/web/src/pages/application/types.ts
Normal file
15
apps/web/src/pages/application/types.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
enum AppStatus {
|
||||
Idle = "Pending",
|
||||
Running = "Running",
|
||||
Stopped = "Stopped",
|
||||
}
|
||||
|
||||
export interface Application {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
git: string;
|
||||
env: Record<string, string>;
|
||||
createdAt: string;
|
||||
status: AppStatus;
|
||||
}
|
||||
73
apps/web/src/pages/home/index.tsx
Normal file
73
apps/web/src/pages/home/index.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Avatar, Layout, Menu } from '@arco-design/web-react';
|
||||
import {
|
||||
IconApps,
|
||||
IconBulb,
|
||||
IconFire,
|
||||
IconMenuFold,
|
||||
IconMenuUnfold,
|
||||
IconRobot,
|
||||
IconSafe,
|
||||
IconUser,
|
||||
} from '@arco-design/web-react/icon';
|
||||
import { useState } from 'react';
|
||||
import Logo from '@assets/images/logo.svg?react';
|
||||
import { Outlet } from 'react-router';
|
||||
|
||||
function Home() {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
return (
|
||||
<Layout className="h-screen w-full">
|
||||
<Layout.Sider
|
||||
collapsible
|
||||
onCollapse={setCollapsed}
|
||||
trigger={collapsed ? <IconMenuUnfold /> : <IconMenuFold />}
|
||||
>
|
||||
<div className="flex flex-row items-center justify-center px-2 py-3">
|
||||
<Logo />
|
||||
{!collapsed && <h2 className="ml-4 text-xl font-medium">Foka CI</h2>}
|
||||
</div>
|
||||
<Menu
|
||||
className="flex-1"
|
||||
defaultOpenKeys={['0']}
|
||||
defaultSelectedKeys={['0_1']}
|
||||
>
|
||||
<Menu.Item key="0">
|
||||
<IconApps />
|
||||
Navigation 1
|
||||
</Menu.Item>
|
||||
<Menu.Item key="1">
|
||||
<IconRobot />
|
||||
Navigation 2
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2">
|
||||
<IconBulb />
|
||||
Navigation 3
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3">
|
||||
<IconSafe />
|
||||
Navigation 4
|
||||
</Menu.Item>
|
||||
<Menu.Item key="4">
|
||||
<IconFire />
|
||||
Navigation 5
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</Layout.Sider>
|
||||
<Layout>
|
||||
<Layout.Header className="h-14 border-b-gray-100 border-b-[1px]">
|
||||
<div className="flex items-center justify-end px-4 h-full">
|
||||
<Avatar>
|
||||
<IconUser />
|
||||
</Avatar>
|
||||
</div>
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<Outlet />
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
18
apps/web/src/pages/login/index.tsx
Normal file
18
apps/web/src/pages/login/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Input, Space } from '@arco-design/web-react';
|
||||
import { IconUser, IconInfoCircle } from '@arco-design/web-react/icon';
|
||||
function Login() {
|
||||
return (
|
||||
<div>
|
||||
<Space direction='vertical'>
|
||||
<Input placeholder="username" prefix={<IconUser />} size="large" />
|
||||
<Input.Password
|
||||
placeholder="password"
|
||||
prefix={<IconInfoCircle />}
|
||||
size="large"
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
1
apps/web/src/styles/index.css
Normal file
1
apps/web/src/styles/index.css
Normal file
@@ -0,0 +1 @@
|
||||
@import 'tailwindcss';
|
||||
30
apps/web/tsconfig.json
Normal file
30
apps/web/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "ES2020"],
|
||||
"jsx": "react-jsx",
|
||||
"target": "ES2020",
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"useDefineForClassFields": true,
|
||||
|
||||
/* modules */
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"moduleResolution": "bundler",
|
||||
"verbatimModuleSyntax": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
|
||||
/* type checking */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"paths": {
|
||||
"@pages/*": ["./src/pages/*"],
|
||||
"@styles/*": ["./src/styles/*"],
|
||||
"@assets/*": ["./src/assets/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user