feat: init
This commit is contained in:
commit
96ca2076ed
34
.cargo/config.toml
Normal file
34
.cargo/config.toml
Normal file
@ -0,0 +1,34 @@
|
||||
[alias]
|
||||
recorder = "run -p recorder --bin recorder_cli -- --environment recorder.development"
|
||||
recorder-playground = "run -p recorder --example playground -- --environment recorder.development"
|
||||
|
||||
[build]
|
||||
rustflags = ["-Zthreads=8"]
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-Zthreads=8", "-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"]
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld.exe"
|
||||
rustflags = ["-Zthreads=8", "-Zshare-generics=n"]
|
||||
|
||||
# NOTE: you must install [Mach-O LLD Port](https://lld.llvm.org/MachO/index.html) on mac. you can easily do this by installing llvm which includes lld with the "brew" package manager:
|
||||
# `brew install llvm`
|
||||
#[target.x86_64-apple-darwin]
|
||||
#rustflags = [
|
||||
# "-Zthreads=8",
|
||||
# "-C",
|
||||
# "link-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld",
|
||||
# "-Zshare-generics=y",
|
||||
#]
|
||||
|
||||
# NOTE: you must install [Mach-O LLD Port](https://lld.llvm.org/MachO/index.html) on mac. you can easily do this by installing llvm which includes lld with the "brew" package manager:
|
||||
# `brew install llvm`
|
||||
#[target.aarch64-apple-darwin]
|
||||
#rustflags = [
|
||||
# "-Zthreads=8",
|
||||
# "-C",
|
||||
# "link-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld",
|
||||
# "-Zshare-generics=y",
|
||||
#]
|
222
.gitignore
vendored
Normal file
222
.gitignore
vendored
Normal file
@ -0,0 +1,222 @@
|
||||
### VisualStudioCode template
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
.idea
|
||||
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# 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 tests 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
|
||||
|
||||
# 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 variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# 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
|
||||
|
||||
# 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.*
|
||||
|
||||
index.js
|
||||
index.js.map
|
||||
index.d.ts
|
||||
index.d.ts.map
|
||||
|
||||
**/dist
|
||||
**/preview_dist
|
||||
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
/examples/*
|
||||
!/examples/.gitkeep
|
||||
/.env
|
||||
/.env.bk
|
||||
/.angular
|
||||
/*.session.sql
|
5205
Cargo.lock
generated
Normal file
5205
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
Cargo.toml
Normal file
3
Cargo.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[workspace]
|
||||
members = ["crates/recorder"]
|
||||
resolver = "2"
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Zhou Yeheng
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
5
apps/proxy/.whistle/.gitignore
vendored
Normal file
5
apps/proxy/.whistle/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
**/.backup
|
||||
**/.recycle_bin
|
||||
**/.clientid
|
||||
/properties/*
|
||||
!/properties/properties
|
1
apps/proxy/.whistle/properties/properties
Normal file
1
apps/proxy/.whistle/properties/properties
Normal file
@ -0,0 +1 @@
|
||||
{"filesOrder":["latestVersion"],"Custom1":"Custom1","Custom2":"Custom2","allowMultipleChoice":true}
|
2
apps/proxy/.whistle/rules/files/0.webui
Normal file
2
apps/proxy/.whistle/rules/files/0.webui
Normal file
@ -0,0 +1,2 @@
|
||||
^https://webui.konobangu.com/*** http://127.0.0.1:3000/$1
|
||||
^wss://webui.konobangu.com/*** ws://127.0.0.1:3000/$1
|
1
apps/proxy/.whistle/rules/files/2.recorder
Normal file
1
apps/proxy/.whistle/rules/files/2.recorder
Normal file
@ -0,0 +1 @@
|
||||
^https://recorder.konobangu.com/*** http://127.0.0.1:7600/$1
|
1
apps/proxy/.whistle/rules/properties
Normal file
1
apps/proxy/.whistle/rules/properties
Normal file
@ -0,0 +1 @@
|
||||
{"filesOrder":["webui","recorder"],"selectedList":["webui","recorder"],"disabledDefalutRules":true}
|
0
apps/proxy/.whistle/values/properties
Normal file
0
apps/proxy/.whistle/values/properties
Normal file
13
apps/proxy/package.json
Normal file
13
apps/proxy/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "proxy",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "whistle run -p 8899 -t 30000 -M \"keepXFF|prod|capture\" -D . --no-global-plugins"
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"whistle": "^2.9.61"
|
||||
}
|
||||
}
|
6
apps/webui/.eslintrc.mjs
Normal file
6
apps/webui/.eslintrc.mjs
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-imports": "error"
|
||||
}
|
||||
}
|
36
apps/webui/.gitignore
vendored
Normal file
36
apps/webui/.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
36
apps/webui/README.md
Normal file
36
apps/webui/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
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.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
BIN
apps/webui/app/favicon.ico
Normal file
BIN
apps/webui/app/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
76
apps/webui/app/globals.css
Normal file
76
apps/webui/app/globals.css
Normal file
@ -0,0 +1,76 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
22
apps/webui/app/layout.tsx
Normal file
22
apps/webui/app/layout.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
113
apps/webui/app/page.tsx
Normal file
113
apps/webui/app/page.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
|
||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
||||
Get started by editing
|
||||
<code className="font-mono font-bold">app/page.tsx</code>
|
||||
</p>
|
||||
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
|
||||
<a
|
||||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
src="/vercel.svg"
|
||||
alt="Vercel Logo"
|
||||
className="dark:invert"
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
|
||||
<Image
|
||||
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js Logo"
|
||||
width={180}
|
||||
height={37}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Docs{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Find in-depth information about Next.js features and API.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Learn{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Learn about Next.js in an interactive course with quizzes!
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Templates{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Explore starter templates for Next.js.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Deploy{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
|
||||
Instantly deploy your Next.js site to a shareable URL with Vercel.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
17
apps/webui/components.json
Normal file
17
apps/webui/components.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
6
apps/webui/lib/utils.ts
Normal file
6
apps/webui/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
7
apps/webui/next.config.mjs
Normal file
7
apps/webui/next.config.mjs
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @type { import("next").NextConfig }
|
||||
*/
|
||||
const nextConfig = {
|
||||
};
|
||||
|
||||
export default nextConfig;
|
37
apps/webui/package.json
Normal file
37
apps/webui/package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "webui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"i18next": "^23.10.0",
|
||||
"next": "14.1.0",
|
||||
"next-i18next": "^15.2.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-i18next": "^14.0.5",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.0",
|
||||
"grasslands": "^0.1.3",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
6
apps/webui/postcss.config.js
Normal file
6
apps/webui/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
1
apps/webui/public/next.svg
Normal file
1
apps/webui/public/next.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
apps/webui/public/vercel.svg
Normal file
1
apps/webui/public/vercel.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
After Width: | Height: | Size: 629 B |
80
apps/webui/tailwind.config.ts
Normal file
80
apps/webui/tailwind.config.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import type { Config } from "tailwindcss"
|
||||
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
prefix: "",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
} satisfies Config
|
||||
|
||||
export default config
|
40
apps/webui/tsconfig.json
Normal file
40
apps/webui/tsconfig.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"next.config.mjs"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
113
config/recorder.development.yaml
Normal file
113
config/recorder.development.yaml
Normal file
@ -0,0 +1,113 @@
|
||||
# Loco configuration file documentation
|
||||
|
||||
# Application logging configuration
|
||||
logger:
|
||||
# Enable or disable logging.
|
||||
enable: true
|
||||
# Enable pretty backtrace (sets RUST_BACKTRACE=1)
|
||||
pretty_backtrace: true
|
||||
# Log level, options: trace, debug, info, warn or error.
|
||||
level: debug
|
||||
# Define the logging format. options: compact, pretty or Json
|
||||
format: compact
|
||||
# By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries
|
||||
# Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters.
|
||||
# override_filter: trace
|
||||
|
||||
# Web server configuration
|
||||
server:
|
||||
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
|
||||
port: 3001
|
||||
# The UI hostname or IP address that mailers will point to.
|
||||
host: http://webui.konobangu.com
|
||||
# Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block
|
||||
middlewares:
|
||||
# Enable Etag cache header middleware
|
||||
etag:
|
||||
enable: true
|
||||
# Allows to limit the payload size request. payload that bigger than this file will blocked the request.
|
||||
limit_payload:
|
||||
# Enable/Disable the middleware.
|
||||
enable: true
|
||||
# the limit size. can be b,kb,kib,mb,mib,gb,gib
|
||||
body_limit: 5mb
|
||||
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
|
||||
logger:
|
||||
# Enable/Disable the middleware.
|
||||
enable: true
|
||||
# when your code is panicked, the request still returns 500 status code.
|
||||
catch_panic:
|
||||
# Enable/Disable the middleware.
|
||||
enable: true
|
||||
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
|
||||
timeout_request:
|
||||
# Enable/Disable the middleware.
|
||||
enable: false
|
||||
# Duration time in milliseconds.
|
||||
timeout: 5000
|
||||
cors:
|
||||
enable: true
|
||||
# Set the value of the [`Access-Control-Allow-Origin`][mdn] header
|
||||
# allow_origins:
|
||||
# - https://loco.rs
|
||||
# Set the value of the [`Access-Control-Allow-Headers`][mdn] header
|
||||
# allow_headers:
|
||||
# - Content-Type
|
||||
# Set the value of the [`Access-Control-Allow-Methods`][mdn] header
|
||||
# allow_methods:
|
||||
# - POST
|
||||
# Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds
|
||||
# max_age: 3600
|
||||
|
||||
# Worker Configuration
|
||||
workers:
|
||||
# specifies the worker mode. Options:
|
||||
# - BackgroundQueue - Workers operate asynchronously in the background, processing queued.
|
||||
# - ForegroundBlocking - Workers operate in the foreground and block until tasks are completed.
|
||||
# - BackgroundAsync - Workers operate asynchronously in the background, processing tasks with async capabilities.
|
||||
mode: BackgroundQueue
|
||||
|
||||
# Mailer Configuration.
|
||||
mailer:
|
||||
# SMTP mailer configuration.
|
||||
smtp:
|
||||
# Enable/Disable smtp mailer.
|
||||
enable: true
|
||||
# SMTP server host. e.x localhost, smtp.gmail.com
|
||||
host: '{{ get_env(name="MAILER_HOST", default="localhost") }}'
|
||||
# SMTP server port
|
||||
port: 1025
|
||||
# Use secure connection (SSL/TLS).
|
||||
secure: false
|
||||
# auth:
|
||||
# user:
|
||||
# password:
|
||||
|
||||
# Database Configuration
|
||||
database:
|
||||
# Database connection URI
|
||||
uri: '{{ get_env(name="DATABASE_URL", default="postgres://konobangu:konobangu@localhost:5432/konobangu") }}'
|
||||
# When enabled, the sql query will be logged.
|
||||
enable_logging: false
|
||||
# Set the timeout duration when acquiring a connection.
|
||||
connect_timeout: 500
|
||||
# Set the idle duration before closing a connection.
|
||||
idle_timeout: 500
|
||||
# Minimum number of connections for a pool.
|
||||
min_connections: 1
|
||||
# Maximum number of connections for a pool.
|
||||
max_connections: 1
|
||||
# Run migration up when application loaded
|
||||
auto_migrate: true
|
||||
# Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||
dangerously_truncate: false
|
||||
# Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||
dangerously_recreate: false
|
||||
|
||||
# Redis Configuration
|
||||
redis:
|
||||
# Redis connection URI
|
||||
uri: '{{ get_env(name="REDIS_URL", default="redis://127.0.0.1") }}'
|
||||
# Dangerously flush all data in Redis on startup. dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||
dangerously_flush: false
|
||||
|
0
config/recorder.production.yaml
Normal file
0
config/recorder.production.yaml
Normal file
118
config/recorder.test.yaml
Normal file
118
config/recorder.test.yaml
Normal file
@ -0,0 +1,118 @@
|
||||
# Loco configuration file documentation
|
||||
|
||||
# Application logging configuration
|
||||
logger:
|
||||
# Enable or disable logging.
|
||||
enable: false
|
||||
# Log level, options: trace, debug, info, warn or error.
|
||||
level: debug
|
||||
# Define the logging format. options: compact, pretty or Json
|
||||
format: compact
|
||||
# By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries
|
||||
# Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters.
|
||||
# override_filter: trace
|
||||
|
||||
# Web server configuration
|
||||
server:
|
||||
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
|
||||
port: 3000
|
||||
# The UI hostname or IP address that mailers will point to.
|
||||
host: http://localhost
|
||||
# Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block
|
||||
middlewares:
|
||||
# Allows to limit the payload size request. payload that bigger than this file will blocked the request.
|
||||
limit_payload:
|
||||
# Enable/Disable the middleware.
|
||||
enable: true
|
||||
# the limit size. can be b,kb,kib,mb,mib,gb,gib
|
||||
body_limit: 5mb
|
||||
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
|
||||
logger:
|
||||
# Enable/Disable the middleware.
|
||||
enable: true
|
||||
# when your code is panicked, the request still returns 500 status code.
|
||||
catch_panic:
|
||||
# Enable/Disable the middleware.
|
||||
enable: true
|
||||
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
|
||||
timeout_request:
|
||||
# Enable/Disable the middleware.
|
||||
enable: false
|
||||
# Duration time in milliseconds.
|
||||
timeout: 5000
|
||||
cors:
|
||||
enable: true
|
||||
# Set the value of the [`Access-Control-Allow-Origin`][mdn] header
|
||||
# allow_origins:
|
||||
# - https://loco.rs
|
||||
# Set the value of the [`Access-Control-Allow-Headers`][mdn] header
|
||||
# allow_headers:
|
||||
# - Content-Type
|
||||
# Set the value of the [`Access-Control-Allow-Methods`][mdn] header
|
||||
# allow_methods:
|
||||
# - POST
|
||||
# Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds
|
||||
# max_age: 3600
|
||||
|
||||
# Worker Configuration
|
||||
workers:
|
||||
# specifies the worker mode. Options:
|
||||
# - BackgroundQueue - Workers operate asynchronously in the background, processing queued.
|
||||
# - ForegroundBlocking - Workers operate in the foreground and block until tasks are completed.
|
||||
# - BackgroundAsync - Workers operate asynchronously in the background, processing tasks with async capabilities.
|
||||
mode: ForegroundBlocking
|
||||
|
||||
# Mailer Configuration.
|
||||
mailer:
|
||||
# SMTP mailer configuration.
|
||||
smtp:
|
||||
# Enable/Disable smtp mailer.
|
||||
enable: true
|
||||
# SMTP server host. e.x localhost, smtp.gmail.com
|
||||
host: localhost
|
||||
# SMTP server port
|
||||
port: 1025
|
||||
# Use secure connection (SSL/TLS).
|
||||
secure: false
|
||||
# auth:
|
||||
# user:
|
||||
# password:
|
||||
stub: true
|
||||
|
||||
# Database Configuration
|
||||
database:
|
||||
# Database connection URI
|
||||
uri: {{get_env(name="DATABASE_URL", default="postgres://loco:loco@localhost:5432/recorder_test")}}
|
||||
# When enabled, the sql query will be logged.
|
||||
enable_logging: false
|
||||
# Set the timeout duration when acquiring a connection.
|
||||
connect_timeout: 500
|
||||
# Set the idle duration before closing a connection.
|
||||
idle_timeout: 500
|
||||
# Minimum number of connections for a pool.
|
||||
min_connections: 1
|
||||
# Maximum number of connections for a pool.
|
||||
max_connections: 1
|
||||
# Run migration up when application loaded
|
||||
auto_migrate: true
|
||||
# Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||
dangerously_truncate: true
|
||||
# Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||
dangerously_recreate: false
|
||||
|
||||
# Redis Configuration
|
||||
redis:
|
||||
# Redis connection URI
|
||||
uri: {{get_env(name="REDIS_URL", default="redis://127.0.0.1")}}
|
||||
# Dangerously flush all data in Redis on startup. dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||
dangerously_flush: false
|
||||
|
||||
# Authentication Configuration
|
||||
auth:
|
||||
# JWT authentication
|
||||
jwt:
|
||||
# Secret key for token generation and verification
|
||||
secret: ZknFYqXpnDgaWcKJZ5J5
|
||||
# Token expiration time in seconds
|
||||
expiration: 604800 # 7 days
|
||||
|
8
crates/recorder/.devcontainer/Dockerfile
Normal file
8
crates/recorder/.devcontainer/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/rust:0-1
|
||||
|
||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -y install --no-install-recommends postgresql-client \
|
||||
&& cargo install sea-orm-cli cargo-insta \
|
||||
&& chown -R vscode /usr/local/cargo
|
||||
|
||||
COPY .env /.env
|
9
crates/recorder/.devcontainer/devcontainer.json
Normal file
9
crates/recorder/.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Konobangu Recorder",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
"forwardPorts": [
|
||||
3001
|
||||
]
|
||||
}
|
40
crates/recorder/.devcontainer/docker-compose.yml
Normal file
40
crates/recorder/.devcontainer/docker-compose.yml
Normal file
@ -0,0 +1,40 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
command: sleep infinity
|
||||
networks:
|
||||
- db
|
||||
- redis
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
env_file:
|
||||
- .env
|
||||
db:
|
||||
image: postgres:15.3-alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 5432:5432
|
||||
networks:
|
||||
- db
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- .env
|
||||
redis:
|
||||
image: redis:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 6379:6379
|
||||
networks:
|
||||
- redis
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
|
||||
networks:
|
||||
db:
|
||||
redis:
|
107
crates/recorder/.github/workflows/ci.yaml
vendored
Normal file
107
crates/recorder/.github/workflows/ci.yaml
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
RUST_TOOLCHAIN: stable
|
||||
TOOLCHAIN_PROFILE: minimal
|
||||
|
||||
jobs:
|
||||
rustfmt:
|
||||
name: Check Style
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: ${{ env.TOOLCHAIN_PROFILE }}
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
components: rustfmt
|
||||
- name: Run cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
name: Run Clippy
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: ${{ env.TOOLCHAIN_PROFILE }}
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Run cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms
|
||||
|
||||
test:
|
||||
name: Run Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- "6379:6379"
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_DB: postgress_test
|
||||
POSTGRES_USER: postgress
|
||||
POSTGRES_PASSWORD: postgress
|
||||
ports:
|
||||
- "5432:5432"
|
||||
# Set health checks to wait until postgres has started
|
||||
options: --health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: ${{ env.TOOLCHAIN_PROFILE }}
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Run cargo test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features --all
|
||||
env:
|
||||
REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}}
|
||||
DATABASE_URL: postgres://postgress:postgress@localhost:5432/postgress_test
|
17
crates/recorder/.gitignore
vendored
Normal file
17
crates/recorder/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
**/config/local.yaml
|
||||
**/config/*.local.yaml
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
50
crates/recorder/Cargo.toml
Normal file
50
crates/recorder/Cargo.toml
Normal file
@ -0,0 +1,50 @@
|
||||
[package]
|
||||
name = "recorder"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
loco-rs = { version = "0.3.1" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
eyre = "0.6"
|
||||
tokio = { version = "1.33.0", default-features = false }
|
||||
async-trait = "0.1.74"
|
||||
tracing = "0.1.40"
|
||||
chrono = "0.4"
|
||||
validator = { version = "0.16" }
|
||||
sea-orm = { version = "1.0.0-rc.1", features = [
|
||||
"sqlx-sqlite",
|
||||
"sqlx-postgres",
|
||||
"runtime-tokio-rustls",
|
||||
"macros",
|
||||
] }
|
||||
|
||||
axum = "0.7.1"
|
||||
include_dir = "0.7"
|
||||
uuid = { version = "1.6.0", features = ["v4"] }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] }
|
||||
sea-orm-migration = { version = "1.0.0-rc.1", features = [
|
||||
"runtime-tokio-rustls",
|
||||
] }
|
||||
reqwest = "0.11.24"
|
||||
thiserror = "1.0.57"
|
||||
rss = "2.0.7"
|
||||
|
||||
[lib]
|
||||
name = "recorder"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "recorder_cli"
|
||||
path = "src/bin/main.rs"
|
||||
required-features = []
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "2.0.0"
|
||||
rstest = "0.18.2"
|
||||
loco-rs = { version = "0.3.1", features = ["testing"] }
|
||||
insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] }
|
33
crates/recorder/examples/playground.rs
Normal file
33
crates/recorder/examples/playground.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use eyre::Context;
|
||||
#[allow(unused_imports)]
|
||||
use loco_rs::{cli::playground, prelude::*};
|
||||
use recorder::app::App;
|
||||
|
||||
async fn fetch_and_parse_rss_demo () -> eyre::Result<()> {
|
||||
let url =
|
||||
"https://mikanani.me/RSS/MyBangumi?token=FE9tccsML2nBPUUqpCuJW2uJZydAXCntHJ7RpD9LDP8%3d";
|
||||
|
||||
let res = reqwest::get(url).await?.bytes().await?;
|
||||
let channel = rss::Channel::read_from(&res[..])?;
|
||||
println!("channel: {:#?}", channel);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
|
||||
fetch_and_parse_rss_demo().await?;
|
||||
|
||||
// let active_model: articles::ActiveModel = ActiveModel {
|
||||
// title: Set(Some("how to build apps in 3 steps".to_string())),
|
||||
// content: Set(Some("use Loco: https://loco.rs".to_string())),
|
||||
// ..Default::default()
|
||||
// };
|
||||
// active_model.insert(&ctx.db).await.unwrap();
|
||||
|
||||
// let res = articles::Entity::find().all(&ctx.db).await.unwrap();
|
||||
// println!("{:?}", res);
|
||||
println!("welcome to playground. edit me at `examples/playground.rs`");
|
||||
|
||||
Ok(())
|
||||
}
|
62
crates/recorder/src/app.rs
Normal file
62
crates/recorder/src/app.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use std::path::Path;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use loco_rs::{
|
||||
app::{AppContext, Hooks},
|
||||
boot::{create_app, BootResult, StartMode},
|
||||
controller::AppRoutes,
|
||||
db::truncate_table,
|
||||
environment::Environment,
|
||||
task::Tasks,
|
||||
worker::{AppWorker, Processor},
|
||||
Result,
|
||||
};
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
use crate::{
|
||||
controllers, migrations::Migrator, models::_entities::subscribers,
|
||||
workers::downloader::DownloadWorker,
|
||||
};
|
||||
|
||||
pub struct App;
|
||||
#[async_trait]
|
||||
impl Hooks for App {
|
||||
fn app_name() -> &'static str {
|
||||
env!("CARGO_CRATE_NAME")
|
||||
}
|
||||
|
||||
fn app_version() -> String {
|
||||
format!(
|
||||
"{} ({})",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
option_env!("BUILD_SHA")
|
||||
.or(option_env!("GITHUB_SHA"))
|
||||
.unwrap_or("dev")
|
||||
)
|
||||
}
|
||||
|
||||
async fn boot(mode: StartMode, environment: &Environment) -> Result<BootResult> {
|
||||
create_app::<Self, Migrator>(mode, environment).await
|
||||
}
|
||||
|
||||
fn routes(_ctx: &AppContext) -> AppRoutes {
|
||||
AppRoutes::with_default_routes()
|
||||
.prefix("/api")
|
||||
.add_route(controllers::subscribers::routes())
|
||||
}
|
||||
|
||||
fn connect_workers<'a>(p: &'a mut Processor, ctx: &'a AppContext) {
|
||||
p.register(DownloadWorker::build(ctx));
|
||||
}
|
||||
|
||||
fn register_tasks(_tasks: &mut Tasks) {}
|
||||
|
||||
async fn truncate(db: &DatabaseConnection) -> Result<()> {
|
||||
truncate_table(db, subscribers::Entity).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn seed(_db: &DatabaseConnection, _base: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
8
crates/recorder/src/bin/main.rs
Normal file
8
crates/recorder/src/bin/main.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use loco_rs::cli;
|
||||
use recorder::migrations::Migrator;
|
||||
use recorder::app::App;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
cli::main::<App, Migrator>().await
|
||||
}
|
1
crates/recorder/src/controllers/mod.rs
Normal file
1
crates/recorder/src/controllers/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod subscribers;
|
14
crates/recorder/src/controllers/subscribers.rs
Normal file
14
crates/recorder/src/controllers/subscribers.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use loco_rs::prelude::*;
|
||||
|
||||
use crate::{models::_entities::subscribers, views::subscribers::CurrentResponse};
|
||||
|
||||
async fn current(State(ctx): State<AppContext>) -> Result<Json<CurrentResponse>> {
|
||||
let subscriber = subscribers::Model::find_root(&ctx.db).await?;
|
||||
format::json(CurrentResponse::new(&subscriber))
|
||||
}
|
||||
|
||||
pub fn routes() -> Routes {
|
||||
Routes::new()
|
||||
.prefix("subscribers")
|
||||
.add("/current", get(current))
|
||||
}
|
0
crates/recorder/src/downloader/aria.rs
Normal file
0
crates/recorder/src/downloader/aria.rs
Normal file
2
crates/recorder/src/downloader/mod.rs
Normal file
2
crates/recorder/src/downloader/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod aria;
|
||||
pub mod qbitorrent;
|
0
crates/recorder/src/downloader/qbitorrent.rs
Normal file
0
crates/recorder/src/downloader/qbitorrent.rs
Normal file
10
crates/recorder/src/lib.rs
Normal file
10
crates/recorder/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
||||
pub mod app;
|
||||
pub mod controllers;
|
||||
pub mod downloader;
|
||||
pub mod migrations;
|
||||
pub mod models;
|
||||
pub mod rss;
|
||||
pub mod subscriptions;
|
||||
pub mod tasks;
|
||||
pub mod views;
|
||||
pub mod workers;
|
40
crates/recorder/src/migrations/defs.rs
Normal file
40
crates/recorder/src/migrations/defs.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum Subscribers {
|
||||
Table,
|
||||
Id,
|
||||
Pid,
|
||||
DisplayName,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum Subscriptions {
|
||||
Table,
|
||||
Id,
|
||||
SubscriberId,
|
||||
DisplayName,
|
||||
Category,
|
||||
SourceUrl,
|
||||
Aggregate,
|
||||
Enabled,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum Bangumi {
|
||||
Table,
|
||||
Id,
|
||||
DisplayName,
|
||||
SubscriptionId,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum Episodes {
|
||||
Table,
|
||||
Id,
|
||||
DisplayName,
|
||||
BangumiId,
|
||||
DownloadUrl,
|
||||
DownloadProgress,
|
||||
OutputName,
|
||||
}
|
125
crates/recorder/src/migrations/m20220101_000001_init.rs
Normal file
125
crates/recorder/src/migrations/m20220101_000001_init.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use sea_orm::sea_query::extension::postgres::Type;
|
||||
use sea_orm_migration::{prelude::*, schema::*};
|
||||
|
||||
use super::defs::{Bangumi, Episodes, Subscribers, Subscriptions};
|
||||
use crate::models::subscribers::ROOT_SUBSCRIBER;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
table_auto(Subscribers::Table)
|
||||
.col(pk_auto(Subscribers::Id))
|
||||
.col(string_len_uniq(Subscribers::Pid, 64))
|
||||
.col(string(Subscribers::DisplayName))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let insert = Query::insert()
|
||||
.into_table(Subscribers::Table)
|
||||
.columns([Subscribers::Pid, Subscribers::DisplayName])
|
||||
.values_panic([ROOT_SUBSCRIBER.into(), ROOT_SUBSCRIBER.into()])
|
||||
.to_owned();
|
||||
manager.exec_stmt(insert).await?;
|
||||
|
||||
manager
|
||||
.create_type(
|
||||
Type::create()
|
||||
.as_enum(Alias::new("subscription_category"))
|
||||
.values([
|
||||
Alias::new("mikan"),
|
||||
Alias::new("manual"),
|
||||
Alias::new("bangumi"),
|
||||
])
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
table_auto(Subscriptions::Table)
|
||||
.col(pk_auto(Subscriptions::Id))
|
||||
.col(string(Subscriptions::DisplayName))
|
||||
.col(integer(Subscriptions::SubscriberId))
|
||||
.col(text(Subscriptions::SourceUrl))
|
||||
.col(boolean(Subscriptions::Aggregate))
|
||||
.col(boolean(Subscriptions::Enabled))
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("subscription_subscriber_id")
|
||||
.from(Subscriptions::Table, Subscriptions::SubscriberId)
|
||||
.to(Subscribers::Table, Subscribers::Id),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
table_auto(Bangumi::Table)
|
||||
.col(pk_auto(Bangumi::Id))
|
||||
.col(text(Bangumi::DisplayName))
|
||||
.col(integer(Bangumi::SubscriptionId))
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("bangumi_subscription_id")
|
||||
.from(Bangumi::Table, Bangumi::SubscriptionId)
|
||||
.to(Subscriptions::Table, Subscriptions::Id),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
table_auto(Episodes::Table)
|
||||
.col(pk_auto(Episodes::Id))
|
||||
.col(text(Episodes::DisplayName))
|
||||
.col(integer(Episodes::BangumiId))
|
||||
.col(text(Episodes::DownloadUrl))
|
||||
.col(tiny_integer(Episodes::DownloadProgress).default(0))
|
||||
.col(text(Episodes::OutputName))
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("episode_bangumi_id")
|
||||
.from(Episodes::Table, Episodes::BangumiId)
|
||||
.to(Bangumi::Table, Bangumi::Id),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Episodes::Table).to_owned())
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.drop_table(Table::drop().table(Bangumi::Table).to_owned())
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.drop_table(Table::drop().table(Subscriptions::Table).to_owned())
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.drop_type(
|
||||
Type::drop()
|
||||
.name(Alias::new("subscription_category"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.drop_table(Table::drop().table(Subscribers::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
13
crates/recorder/src/migrations/mod.rs
Normal file
13
crates/recorder/src/migrations/mod.rs
Normal file
@ -0,0 +1,13 @@
|
||||
pub use sea_orm_migration::prelude::*;
|
||||
|
||||
pub mod defs;
|
||||
pub mod m20220101_000001_init;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![Box::new(m20220101_000001_init::Migration)]
|
||||
}
|
||||
}
|
39
crates/recorder/src/models/_entities/bangumi.rs
Normal file
39
crates/recorder/src/models/_entities/bangumi.rs
Normal file
@ -0,0 +1,39 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "bangumi")]
|
||||
pub struct Model {
|
||||
pub created_at: DateTime,
|
||||
pub updated_at: DateTime,
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub display_name: String,
|
||||
pub subscription_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::subscriptions::Entity",
|
||||
from = "Column::SubscriptionId",
|
||||
to = "super::subscriptions::Column::Id"
|
||||
)]
|
||||
Subscription,
|
||||
#[sea_orm(has_many = "super::episodes::Entity")]
|
||||
Episode,
|
||||
}
|
||||
|
||||
impl Related<super::episodes::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Episode.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::subscriptions::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Subscription.def()
|
||||
}
|
||||
}
|
34
crates/recorder/src/models/_entities/episodes.rs
Normal file
34
crates/recorder/src/models/_entities/episodes.rs
Normal file
@ -0,0 +1,34 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "episodes")]
|
||||
pub struct Model {
|
||||
pub created_at: DateTime,
|
||||
pub updated_at: DateTime,
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub display_name: String,
|
||||
pub bangumi_id: i32,
|
||||
pub download_url: String,
|
||||
pub download_progress: i32,
|
||||
pub output_name: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::bangumi::Entity",
|
||||
from = "Column::BangumiId",
|
||||
to = "super::bangumi::Column::Id"
|
||||
)]
|
||||
Bangumi,
|
||||
}
|
||||
|
||||
impl Related<super::bangumi::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Bangumi.def()
|
||||
}
|
||||
}
|
8
crates/recorder/src/models/_entities/mod.rs
Normal file
8
crates/recorder/src/models/_entities/mod.rs
Normal file
@ -0,0 +1,8 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub mod bangumi;
|
||||
pub mod episodes;
|
||||
pub mod subscribers;
|
||||
pub mod subscriptions;
|
6
crates/recorder/src/models/_entities/prelude.rs
Normal file
6
crates/recorder/src/models/_entities/prelude.rs
Normal file
@ -0,0 +1,6 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4
|
||||
|
||||
pub use super::{
|
||||
bangumi::Entity as Bangumi, episodes::Entity as Episodes, subscribers::Entity as Subscribers,
|
||||
subscriptions::Entity as Subscriptions,
|
||||
};
|
28
crates/recorder/src/models/_entities/subscribers.rs
Normal file
28
crates/recorder/src/models/_entities/subscribers.rs
Normal file
@ -0,0 +1,28 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "subscribers")]
|
||||
pub struct Model {
|
||||
pub created_at: DateTime,
|
||||
pub updated_at: DateTime,
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
#[sea_orm(unique)]
|
||||
pub pid: String,
|
||||
pub display_name: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::subscriptions::Entity")]
|
||||
Subscription,
|
||||
}
|
||||
|
||||
impl Related<super::subscriptions::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Subscription.def()
|
||||
}
|
||||
}
|
59
crates/recorder/src/models/_entities/subscriptions.rs
Normal file
59
crates/recorder/src/models/_entities/subscriptions.rs
Normal file
@ -0,0 +1,59 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
|
||||
#[sea_orm(
|
||||
rs_type = "String",
|
||||
db_type = "Enum",
|
||||
enum_name = "subscription_category"
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SubscriptionCategory {
|
||||
#[sea_orm(string_value = "mikan")]
|
||||
Mikan,
|
||||
#[sea_orm(string_value = "manual")]
|
||||
Manual,
|
||||
#[sea_orm(string_value = "bangumi")]
|
||||
Bangumi,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "subscriptions")]
|
||||
pub struct Model {
|
||||
pub created_at: DateTime,
|
||||
pub updated_at: DateTime,
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub display_name: String,
|
||||
pub subscriber_id: i32,
|
||||
pub category: SubscriptionCategory,
|
||||
pub source_url: String,
|
||||
pub aggregate: bool,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::subscribers::Entity",
|
||||
from = "Column::SubscriberId",
|
||||
to = "super::subscribers::Column::Id"
|
||||
)]
|
||||
Subscriber,
|
||||
#[sea_orm(has_many = "super::bangumi::Entity")]
|
||||
Bangumi,
|
||||
}
|
||||
|
||||
impl Related<super::subscribers::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Subscriber.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::bangumi::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Bangumi.def()
|
||||
}
|
||||
}
|
6
crates/recorder/src/models/bangumi.rs
Normal file
6
crates/recorder/src/models/bangumi.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
pub use super::_entities::bangumi::{self, ActiveModel, Entity, Model};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ActiveModelBehavior for super::_entities::bangumi::ActiveModel {}
|
6
crates/recorder/src/models/episodes.rs
Normal file
6
crates/recorder/src/models/episodes.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
pub use super::_entities::episodes::{self, ActiveModel, Entity, Model};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ActiveModelBehavior for super::_entities::episodes::ActiveModel {}
|
5
crates/recorder/src/models/mod.rs
Normal file
5
crates/recorder/src/models/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod _entities;
|
||||
pub mod bangumi;
|
||||
pub mod episodes;
|
||||
pub mod subscribers;
|
||||
pub mod subscriptions;
|
70
crates/recorder/src/models/subscribers.rs
Normal file
70
crates/recorder/src/models/subscribers.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use loco_rs::model::{ModelError, ModelResult};
|
||||
use sea_orm::{entity::prelude::*, ActiveValue, TransactionTrait};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use super::_entities::subscribers::{self, ActiveModel, Entity, Model};
|
||||
|
||||
pub const ROOT_SUBSCRIBER: &str = "konobangu";
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct SubscriberIdParams {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ActiveModelBehavior for super::_entities::subscribers::ActiveModel {
|
||||
async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
|
||||
where
|
||||
C: ConnectionTrait,
|
||||
{
|
||||
if insert {
|
||||
let mut this = self;
|
||||
this.pid = ActiveValue::Set(Uuid::new_v4().to_string());
|
||||
Ok(this)
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::_entities::subscribers::Model {
|
||||
/// finds a user by the provided pid
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// When could not find user or DB query error
|
||||
pub async fn find_by_pid(db: &DatabaseConnection, pid: &str) -> ModelResult<Self> {
|
||||
let parse_uuid = Uuid::parse_str(pid).map_err(|e| ModelError::Any(e.into()))?;
|
||||
let subscriber = subscribers::Entity::find()
|
||||
.filter(subscribers::Column::Pid.eq(parse_uuid))
|
||||
.one(db)
|
||||
.await?;
|
||||
subscriber.ok_or_else(|| ModelError::EntityNotFound)
|
||||
}
|
||||
|
||||
pub async fn find_root(db: &DatabaseConnection) -> ModelResult<Self> {
|
||||
Self::find_by_pid(db, ROOT_SUBSCRIBER).await
|
||||
}
|
||||
|
||||
/// Asynchronously creates a user with a password and saves it to the
|
||||
/// database.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// When could not save the user into the DB
|
||||
pub async fn create_root(db: &DatabaseConnection) -> ModelResult<Self> {
|
||||
let txn = db.begin().await?;
|
||||
|
||||
let user = subscribers::ActiveModel {
|
||||
display_name: ActiveValue::set(ROOT_SUBSCRIBER.to_string()),
|
||||
pid: ActiveValue::set(ROOT_SUBSCRIBER.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&txn)
|
||||
.await?;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
}
|
6
crates/recorder/src/models/subscriptions.rs
Normal file
6
crates/recorder/src/models/subscriptions.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
pub use super::_entities::subscriptions::{self, ActiveModel, Entity, Model};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ActiveModelBehavior for super::_entities::subscriptions::ActiveModel {}
|
23
crates/recorder/src/rss/engine.rs
Normal file
23
crates/recorder/src/rss/engine.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::subscriptions::subscriptions;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RssTorrent {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RssEngine {}
|
||||
|
||||
impl RssEngine {
|
||||
// pub async fn get_rss_torrents(
|
||||
// rss_subscription: &subscriptions::ActiveModel,
|
||||
// ) -> eyre::Result<Vec<RssTorrent>> {
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
pub async fn get_torrents(url: &str) -> eyre::Result<rss::Channel> {
|
||||
let content = reqwest::get(url).await?.bytes().await?;
|
||||
let channel: rss::Channel = rss::Channel::read_from(&content[..])?;
|
||||
Ok(channel)
|
||||
}
|
||||
}
|
1
crates/recorder/src/rss/mod.rs
Normal file
1
crates/recorder/src/rss/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod engine;
|
1
crates/recorder/src/subscriptions/bangumi.rs
Normal file
1
crates/recorder/src/subscriptions/bangumi.rs
Normal file
@ -0,0 +1 @@
|
||||
|
23
crates/recorder/src/subscriptions/mikan.rs
Normal file
23
crates/recorder/src/subscriptions/mikan.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use crate::rss::engine::RssEngine;
|
||||
|
||||
pub struct MikanRssCreateDto {
|
||||
pub rss_link: String,
|
||||
pub display_name: String,
|
||||
pub aggregate: bool,
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
pub struct MikanSubscriptionEngine {
|
||||
}
|
||||
|
||||
impl MikanSubscriptionEngine {
|
||||
pub async fn add_rss(create_dto: MikanRssCreateDto) -> eyre::Result<()> {
|
||||
let content = reqwest::get(&create_dto.rss_link).await?.bytes().await?;
|
||||
let channel = rss::Channel::read_from(&content[..])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MikanSubscriptionItem {
|
||||
}
|
2
crates/recorder/src/subscriptions/mod.rs
Normal file
2
crates/recorder/src/subscriptions/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod bangumi;
|
||||
pub mod mikan;
|
1
crates/recorder/src/tasks/mod.rs
Normal file
1
crates/recorder/src/tasks/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
|
1
crates/recorder/src/views/mod.rs
Normal file
1
crates/recorder/src/views/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod subscribers;
|
19
crates/recorder/src/views/subscribers.rs
Normal file
19
crates/recorder/src/views/subscribers.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::_entities::subscribers;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct CurrentResponse {
|
||||
pub pid: String,
|
||||
pub display_name: String,
|
||||
}
|
||||
|
||||
impl CurrentResponse {
|
||||
#[must_use]
|
||||
pub fn new(user: &subscribers::Model) -> Self {
|
||||
Self {
|
||||
pid: user.pid.to_string(),
|
||||
display_name: user.display_name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
43
crates/recorder/src/workers/downloader.rs
Normal file
43
crates/recorder/src/workers/downloader.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use loco_rs::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::models::subscribers;
|
||||
|
||||
pub struct DownloadWorker {
|
||||
pub ctx: AppContext,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Serialize)]
|
||||
pub struct DownloadWorkerArgs {
|
||||
pub user_guid: String,
|
||||
}
|
||||
|
||||
impl worker::AppWorker<DownloadWorkerArgs> for DownloadWorker {
|
||||
fn build(ctx: &AppContext) -> Self {
|
||||
Self { ctx: ctx.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl worker::Worker<DownloadWorkerArgs> for DownloadWorker {
|
||||
async fn perform(&self, args: DownloadWorkerArgs) -> worker::Result<()> {
|
||||
// TODO: Some actual work goes here...
|
||||
println!("================================================");
|
||||
println!("Sending payment report to user {}", args.user_guid);
|
||||
|
||||
sleep(Duration::from_millis(2000)).await;
|
||||
|
||||
let all = subscribers::Entity::find()
|
||||
.all(&self.ctx.db)
|
||||
.await
|
||||
.map_err(Box::from)?;
|
||||
for user in &all {
|
||||
println!("user: {}", user.id);
|
||||
}
|
||||
println!("================================================");
|
||||
Ok(())
|
||||
}
|
||||
}
|
1
crates/recorder/src/workers/mod.rs
Normal file
1
crates/recorder/src/workers/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod downloader;
|
3
crates/recorder/tests/mod.rs
Normal file
3
crates/recorder/tests/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod models;
|
||||
mod requests;
|
||||
mod tasks;
|
1
crates/recorder/tests/models/mod.rs
Normal file
1
crates/recorder/tests/models/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
mod subscribers;
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
source: tests/models/subscribers.rs
|
||||
expression: non_existing_subscriber_results
|
||||
---
|
||||
Err(
|
||||
EntityNotFound,
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
---
|
||||
source: tests/models/subscribers.rs
|
||||
expression: existing_subscriber
|
||||
---
|
||||
Ok(
|
||||
Model {
|
||||
created_at: 2023-11-12T12:34:56.789,
|
||||
updated_at: 2023-11-12T12:34:56.789,
|
||||
id: 1,
|
||||
pid: "11111111-1111-1111-1111-111111111111",
|
||||
display_name: "user1"
|
||||
},
|
||||
)
|
27
crates/recorder/tests/models/subscribers.rs
Normal file
27
crates/recorder/tests/models/subscribers.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use insta::assert_debug_snapshot;
|
||||
use loco_rs::testing;
|
||||
use recorder::{app::App, models::subscribers::Model};
|
||||
use serial_test::serial;
|
||||
|
||||
macro_rules! configure_insta {
|
||||
($($expr:expr),*) => {
|
||||
let mut settings = insta::Settings::clone_current();
|
||||
settings.set_prepend_module_to_snapshot(false);
|
||||
settings.set_snapshot_suffix("users");
|
||||
let _guard = settings.bind_to_scope();
|
||||
};
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn can_find_by_pid() {
|
||||
configure_insta!();
|
||||
|
||||
let boot = testing::boot_test::<App>().await.unwrap();
|
||||
testing::seed::<App>(&boot.app_context.db).await.unwrap();
|
||||
|
||||
let existing_subscriber =
|
||||
Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111").await;
|
||||
|
||||
assert_debug_snapshot!(existing_subscriber);
|
||||
}
|
2
crates/recorder/tests/requests/mod.rs
Normal file
2
crates/recorder/tests/requests/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod notes;
|
||||
mod subscribers;
|
32
crates/recorder/tests/requests/subscribers.rs
Normal file
32
crates/recorder/tests/requests/subscribers.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use insta::{assert_debug_snapshot, with_settings};
|
||||
use loco_rs::testing;
|
||||
use recorder::app::App;
|
||||
use serial_test::serial;
|
||||
|
||||
// TODO: see how to dedup / extract this to app-local test utils
|
||||
// not to framework, because that would require a runtime dep on insta
|
||||
macro_rules! configure_insta {
|
||||
($($expr:expr),*) => {
|
||||
let mut settings = insta::Settings::clone_current();
|
||||
settings.set_prepend_module_to_snapshot(false);
|
||||
settings.set_snapshot_suffix("user_request");
|
||||
let _guard = settings.bind_to_scope();
|
||||
};
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn can_get_current_user() {
|
||||
configure_insta!();
|
||||
|
||||
testing::request::<App, _, _>(|request, _ctx| async move {
|
||||
let response = request.get("/api/user/current").await;
|
||||
|
||||
with_settings!({
|
||||
filters => testing::cleanup_user_model()
|
||||
}, {
|
||||
assert_debug_snapshot!((response.status_code(), response.text()));
|
||||
});
|
||||
})
|
||||
.await;
|
||||
}
|
1
crates/recorder/tests/tasks/mod.rs
Normal file
1
crates/recorder/tests/tasks/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod seed;
|
43
crates/recorder/tests/tasks/seed.rs
Normal file
43
crates/recorder/tests/tasks/seed.rs
Normal file
@ -0,0 +1,43 @@
|
||||
//! This task implements data seeding functionality for initializing new
|
||||
//! development/demo environments.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! Run the task with the following command:
|
||||
//! ```sh
|
||||
//! cargo run task
|
||||
//! ```
|
||||
//!
|
||||
//! To override existing data and reset the data structure, use the following
|
||||
//! command with the `refresh:true` argument:
|
||||
//! ```sh
|
||||
//! cargo run task seed_data refresh:true
|
||||
//! ```
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use loco_rs::{db, prelude::*};
|
||||
use migration::Migrator;
|
||||
use recorder::app::App;
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct SeedData;
|
||||
#[async_trait]
|
||||
impl Task for SeedData {
|
||||
fn task(&self) -> TaskInfo {
|
||||
TaskInfo {
|
||||
name: "seed_data".to_string(),
|
||||
detail: "Task for seeding data".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&self, app_context: &AppContext, vars: &BTreeMap<String, String>) -> Result<()> {
|
||||
let refresh = vars.get("refresh").is_some_and(|refresh| refresh == "true");
|
||||
|
||||
if refresh {
|
||||
db::reset::<Migrator>(&app_context.db).await?;
|
||||
}
|
||||
let path = std::path::Path::new("src/fixtures");
|
||||
db::run_app_seed::<App>(&app_context.db, path).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
18
justfile
Normal file
18
justfile
Normal file
@ -0,0 +1,18 @@
|
||||
set windows-shell := ["pwsh.exe", "-c"]
|
||||
set dotenv-load
|
||||
|
||||
prepare-dev-recorder:
|
||||
cargo install loco-cli
|
||||
cargo install sea-orm-cli
|
||||
|
||||
dev-recorder:
|
||||
cargo watch -w crates/recorder -w config -x 'recorder start'
|
||||
|
||||
play-recorder:
|
||||
cargo recorder-playground
|
||||
|
||||
dev-webui:
|
||||
npm run dev:webui
|
||||
|
||||
dev-proxy:
|
||||
npm run dev:proxy
|
34
package.json
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "konobangu",
|
||||
"version": "0.0.0",
|
||||
"description": "Kono bangumi?",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"apps/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "npm-run-all -p dev:proxy dev:webui",
|
||||
"dev:proxy": "npm run start -w apps/proxy",
|
||||
"dev:webui": "npm run dev -w apps/webui",
|
||||
"build:webui": "npm run build -w apps/webui"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lonelyhentxi/konobangu.git"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@datasert/cronjs-parser": "^1.4.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^5.0.5",
|
||||
"rxjs": "~7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.4.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"shx": "^0.3.4",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
4190
pnpm-lock.yaml
generated
Normal file
4190
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- "packages/*"
|
||||
- "apps/*"
|
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rustfmt", "clippy"]
|
||||
profile = "default"
|
7
rustfmt.toml
Normal file
7
rustfmt.toml
Normal file
@ -0,0 +1,7 @@
|
||||
max_width = 100
|
||||
comment_width = 80
|
||||
wrap_comments = true
|
||||
imports_granularity = "Crate"
|
||||
use_small_heuristics = "Default"
|
||||
group_imports = "StdExternalCrate"
|
||||
format_strings = true
|
18
src/tasks/rss_dl.rs
Normal file
18
src/tasks/rss_dl.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use loco_rs::prelude::*;
|
||||
|
||||
pub struct RssDl;
|
||||
#[async_trait]
|
||||
impl Task for RssDl {
|
||||
fn task(&self) -> TaskInfo {
|
||||
TaskInfo {
|
||||
name: "rss_dl".to_string(),
|
||||
detail: "Task generator".to_string(),
|
||||
}
|
||||
}
|
||||
async fn run(&self, _app_context: &AppContext, _vars: &BTreeMap<String, String>) -> Result<()> {
|
||||
println!("Task RssDl generated");
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user