5 Commits

Author SHA1 Message Date
b6013ba1e3 DBMS add web/README.md demo website and group info 2021-11-28 23:07:54 +08:00
3457fde522 DBMS Submit static webpage
Removed all fetch function
2021-11-28 18:56:33 +08:00
be2515231c Update: docs/problem_description add extra description 2021-11-25 23:57:54 +08:00
2358335d4e Add: README.md DBMS TODO 2021-11-25 23:57:42 +08:00
1bef4d0272 Add: docs/problem_description.md 2021-11-25 23:55:43 +08:00
27 changed files with 711 additions and 170 deletions

View File

@@ -8,6 +8,53 @@ A light weight personal music streaming platform.
[toc] [toc]
## TODO
- Restructure为多人协作做好准备
### 前端部分更改
- 修复页面 CSS 溢出问题
- 显示操作执行世界
页面数量至少 10 个(目前 5 个),预计添加如下页面
- 文件详情页,可以修改单个文件的信息
- 文件评论页,可对文件进行评论
- 最新动态页,查看最近播放的曲目、最近的评论
- 登录/注册页,取代现有的 token 逻辑
- FfmpegConfigs 配置页面
- 意见反馈的查看页面
### 后端部分更改
- 返回操作执行时间
- 修复 Prepare 模式转码不完整但仍然被 tmpfs 记录为成功转码的问题
- FfmpegConfigs 由目前的字典格式改为列表格式
- 为 sqlite3 添加数据库单线程锁
- 添加外键约束
- Update 功能自动检查重复的项目并忽略,只添加新的项目
- Token 验证方法改为 暱称 + 密码 的方法,管理员使用 admin 保留关键字作为暱称。
需要 8 个 entities 和 6 个 relationship目前有 3 个 entities和 1 个 relationship
目前有
- files 文件表,数量 50,000
- folders 文件夹表,数量 3,000
- feedbacks 反馈留言表
计划添加
- users 用户表
- comments 管理表
- playbacks 播放记录表
- likes 点赞记录表
## 编译 & 构建
## How to build ## How to build
### Build the back-end server ### Build the back-end server

View File

@@ -0,0 +1,36 @@
# DBMS Group Project Problem Description
- Group 1
The Internet infrastructure construction has made the network speed development faster. With the fast Internet, people are gradually migrating various data and services to the cloud. For example, NetEase Cloud Music, Spotify, and Apple Music, we call them streaming media platforms. The definition of streaming media platform is that users purchase the digital copyright of music and then play the music online on the platform.
Generally speaking, users cannot buy music that is not available on the platform. The user cannot download the digital file of the music (the user purchases the right to play instead of the right to copy). Users cannot upload their music to the platform.
However, in the era of digital copyright, there are still many advantages to getting original music files, such as no need to install a dedicated player; free copying to other devices (without violating copyright); no risk of music unavailable from the platform; no play records and privacy will be tracked by the platform.
Some people don't like streaming platforms. They like to collect music (download or buy CDs) and save it on their computers. But as more and more music is collected (over 70,000 songs and in total size of 800GB), it becomes very difficult to manage files. It is difficult for them to find where the songs they want to listen to are saved. Also, lossless music files are large and difficult to play online.
As long as there no such "Self-hosted music streaming platform" software available, we decided to develop a project based on database knowledge to help people who have collected a lot of music to enjoy their music simply.
We will handle various relevant types of data in our database. Including song name, album name, file size, update date, rating, comment, user information, etc. They are highly relevant, so using a relational database will be a good choice.
The features of the project we designed are as follows:
- Open. Independent front-end (GUI) and back-end (server program), using API to communicate.
- Easy to use. Minimize dependencies, allowing users to configure quickly and simply.
- Lightweight. The program is small in size and quick to install.
- High performance. Only do what should be done, no features that will lead to poor performance.
- Cross-platform. The project can run on computers, mobile phones, Linux, Windows, macOS, and X86 and ARM processor architectures.
- Extensibility. Access to cloud OSS (Object Storage Service), reverse proxy, or other external software.
Our project has the following functions:
- Index file. Index local files into the database.
- Search. Search for music based on name/album/tag/comment, sorted by rating or other columns.
- Play. Play music online, play music randomly and play music at a low bit rate on a bad network.
- User management. Users can register and log in.
- Comment. Users can give a like or comment on the music.
- Management. The administrator can upload music, update or delete the database.
- Share. Generate a link to share the music with others.
After research and discussion, in order to meet the above requirements, we decided to use the Golang programming language on the backend. SQLite as a database program. Vue as the front-end GUI interface.

View File

@@ -1,6 +1,39 @@
# Getting Started with Create React App # msw-open-music web font-end
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). This msw-open-music project was bootstrapped with `Create React App`
## Group 9 information
| Name | Name (EN) | No |
| ------ | ------------- | ---------- |
| 陈永源 | CHEN Yongyuan | 1930006025 |
| 鲁雷 | Lu Lei | 2030026101 |
| 张滨玮 | Zhang Binwei | 2030026197 |
| 丁俊超 | Ding Junchao | 2030026258 |
| 邱星越 | Qiu Xingyue | 2030026119 |
| 李真晔 | Li Zhenye | 2030006104 |
## URL References
- `/#/` Default home page. Generate random files.
- `/#/search-files` Search files
- `/#/search-folders` Search folders
- `/#/folder/:id` Show files in the folder
- `/#/file/:id` Show file's information
- `/#/file/:id/share` Share a specific file
- `/#/file/:id/review` Review a file
- `/#/manage` Manage system setting and status
- `/#/login` Login
- `/#/register/` Register
- `/#/profile/:id` Profile of a user
## HOW TO DEPLOY?
Welcome to visit the demo <https://demo.uicbbs.com>
Put the files under `build` folder to your HTTP server (Apache, nginx, caddy, etc.) root folder.
## Available Scripts ## Available Scripts
@@ -14,11 +47,6 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\ The page will reload if you make edits.\
You will also see any lint errors in the console. You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build` ### `npm run build`
Builds the app for production to the `build` folder.\ Builds the app for production to the `build` folder.\
@@ -28,43 +56,3 @@ The build is minified and the filenames include the hashes.\
Your app is ready to be deployed! Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

View File

@@ -15,10 +15,18 @@ body {
box-shadow: 0 0 8px #393939; box-shadow: 0 0 8px #393939;
border-radius: 6px 6px 0 0; border-radius: 6px 6px 0 0;
} }
.avatar {
border-radius: 50%;
background-color: lightpink;
padding: 0.39rem;
}
.title { .title {
margin-left: 1em; margin-left: 1em;
margin-right: 1em;
display: flex; display: flex;
align-items: center; align-items: center;
vertical-align: middle;
justify-content: space-between;
} }
.title-text { .title-text {
margin-left: 1em; margin-left: 1em;

View File

@@ -1,9 +1,4 @@
import { import { HashRouter as Router, Routes, Route, NavLink } from "react-router-dom";
HashRouter as Router,
Routes,
Route,
NavLink,
} from "react-router-dom";
import "./App.css"; import "./App.css";
import GetRandomFiles from "./component/GetRandomFiles"; import GetRandomFiles from "./component/GetRandomFiles";
@@ -12,10 +7,18 @@ import SearchFolders from "./component/SearchFolders";
import Manage from "./component/Manage"; import Manage from "./component/Manage";
import Share from "./component/Share"; import Share from "./component/Share";
import AudioPlayer from "./component/AudioPlayer"; import AudioPlayer from "./component/AudioPlayer";
import FilesInFolder from "./component/FilesInFolder";
import FileInfo from "./component/FileInfo";
import Review from "./component/Review";
import Profile from "./component/Profile";
import User from "./component/User";
import Login from "./component/Login";
import Register from "./component/Register";
import { useState } from "react"; import { useState } from "react";
function App() { function App() {
const [playingFile, setPlayingFile] = useState({}); const [playingFile, setPlayingFile] = useState({});
const [user, setUser] = useState(null);
return ( return (
<div className="base"> <div className="base">
<Router> <Router>
@@ -23,6 +26,7 @@ function App() {
<h3 className="title"> <h3 className="title">
<img src="favicon.png" alt="logo" className="logo" /> <img src="favicon.png" alt="logo" className="logo" />
<span className="title-text">MSW Open Music Project</span> <span className="title-text">MSW Open Music Project</span>
<User user={user} setUser={setUser} />
</h3> </h3>
<nav className="nav"> <nav className="nav">
<NavLink to="/" className="nav-link"> <NavLink to="/" className="nav-link">
@@ -55,14 +59,22 @@ function App() {
element={<SearchFolders setPlayingFile={setPlayingFile} />} element={<SearchFolders setPlayingFile={setPlayingFile} />}
/> />
<Route <Route
path="/search-folders/:id" path="/folder/:id"
element={<SearchFolders setPlayingFile={setPlayingFile} />} element={<FilesInFolder setPlayingFile={setPlayingFile} />}
/> />
<Route path="/manage" element={<Manage />} /> <Route path="/manage" element={<Manage />} />
<Route <Route
path="/share/:id" path="/file/:id/share"
element={<Share setPlayingFile={setPlayingFile} />} element={<Share setPlayingFile={setPlayingFile} />}
/> />
<Route path="/file/:id/review" element={<Review />} />
<Route
path="/profile/:id"
element={<Profile user={user} setUser={setUser} />}
/>
<Route path="/login" element={<Login setUser={setUser} />} />
<Route path="/register" element={<Register setUser={setUser} />} />
<Route path="/file/:id" element={<FileInfo />} />
</Routes> </Routes>
</main> </main>
<footer> <footer>

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import getFfmpegConfigListRespondExample from "../example-respond/get_ffmpeg_config_list.json"
function FfmpegConfig(props) { function FfmpegConfig(props) {
// props.setSelectedFfmpegConfig // props.setSelectedFfmpegConfig
@@ -7,17 +8,8 @@ function FfmpegConfig(props) {
const [ffmpegConfigList, setFfmpegConfigList] = useState([]); const [ffmpegConfigList, setFfmpegConfigList] = useState([]);
useEffect(() => { useEffect(() => {
fetch("/api/v1/get_ffmpeg_config_list") setFfmpegConfigList(getFfmpegConfigListRespondExample.ffmpeg_config_list);
.then((response) => response.json()) props.setSelectedFfmpegConfig(getFfmpegConfigListRespondExample.ffmpeg_config_list[0]);
.then((data) => {
setFfmpegConfigList(data.ffmpeg_config_list);
if (data.ffmpeg_config_list.length > 0) {
props.setSelectedFfmpegConfig(data.ffmpeg_config_list[0]);
}
})
.catch((error) => {
alert("get_ffmpeg_config_list error: " + error);
});
}, []); }, []);
return ( return (

View File

@@ -15,14 +15,11 @@ function FileDialog(props) {
<dialog open={props.showStatus}> <dialog open={props.showStatus}>
<p>{props.file.filename}</p> <p>{props.file.filename}</p>
<p> <p>
Download 使用浏览器下载原文件 Download using browser
<br /> <br />
Play 调用网页播放器播放 Play on the web page
<br /> <br />
</p> </p>
<a href={downloadURL} download>
<button>Download</button>
</a>
<button <button
onClick={() => { onClick={() => {
props.setPlayingFile(props.file); props.setPlayingFile(props.file);
@@ -33,11 +30,10 @@ function FileDialog(props) {
</button> </button>
<button <button
onClick={() => { onClick={() => {
navigate(`/share/${props.file.id}`); navigate(`/file/${props.file.id}`);
props.setShowStatus(false);
}} }}
> >
Share Info
</button> </button>
<button onClick={() => props.setShowStatus(false)}>Close</button> <button onClick={() => props.setShowStatus(false)}>Close</button>
</dialog> </dialog>

View File

@@ -25,7 +25,7 @@ function FileEntry(props) {
</td> </td>
<td <td
className="clickable" className="clickable"
onClick={() => navigate(`/search-folders/${props.file.folder_id}`)} onClick={() => navigate(`/folder/${props.file.folder_id}`)}
> >
{props.file.foldername} {props.file.foldername}
</td> </td>

View File

@@ -0,0 +1,69 @@
import { useParams, Link, useNavigate } from "react-router-dom";
function FileInfo() {
let params = useParams();
let navigate = useNavigate();
return (
<div className="page">
<h3>File Information</h3>
<span>
<button>Download</button>
<button
onClick={() => {
navigate("/file/" + params.id + '/share');
}}
>
Share
</button>
<button
onClick={() => {
navigate("/file/" + params.id + '/review');
}}
>Review</button>
</span>
<table>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>File Name</td>
<td>{params.id}</td>
</tr>
<tr>
<td>File Size</td>
<td>123456</td>
</tr>
<tr>
<td>File Type</td>
<td>media/aac</td>
</tr>
<tr>
<td>Last Modified</td>
<td>2020-01-01</td>
</tr>
<tr>
<td>Import by</td>
<td>
<Link to="/profile/3">@admin</Link>
</td>
</tr>
<tr>
<td>Import Date</td>
<td>2020-01-01</td>
</tr>
<tr>
<td>Location</td>
<td>/data/media/aac</td>
</tr>
</tbody>
</table>
<button>Update</button>
</div>
);
}
export default FileInfo;

View File

@@ -0,0 +1,15 @@
import FilesTable from "./FilesTable";
import searchFilesRespondExample from "../example-respond/search_files.json"
import {useParams} from "react-router";
function FilesInFolder(props) {
let params = useParams();
return (
<div>
<h3>Files in folder id {params.id}</h3>
<FilesTable setPlayingFile={props.setPlayingFile} files={searchFilesRespondExample.files} />
</div>
);
}
export default FilesInFolder;

View File

@@ -14,12 +14,12 @@ function FoldersTable(props) {
{props.folders.map((folder) => ( {props.folders.map((folder) => (
<tr key={folder.id}> <tr key={folder.id}>
<td <td
onClick={() => navigate(`/search-folders/${folder.id}`)} onClick={() => navigate(`/folder/${folder.id}`)}
className="clickable" className="clickable"
> >
{folder.foldername} {folder.foldername}
</td> </td>
<td onClick={() => navigate(`/search-folders/${folder.id}`)}> <td onClick={() => navigate(`/folder/${folder.id}`)}>
<button>View</button> <button>View</button>
</td> </td>
</tr> </tr>

View File

@@ -1,23 +1,13 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import FilesTable from "./FilesTable"; import FilesTable from "./FilesTable";
import getRandomFilesRespondExample from "../example-respond/get_random_files.json"
function GetRandomFiles(props) { function GetRandomFiles(props) {
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
function refresh(setFiles) { function refresh(setFiles) {
setIsLoading(true); setFiles(getRandomFilesRespondExample.files);
fetch("/api/v1/get_random_files")
.then((response) => response.json())
.then((data) => {
setFiles(data.files);
})
.catch((error) => {
alert("get_random_files error: " + error);
})
.finally(() => {
setIsLoading(false);
});
} }
useEffect(() => { useEffect(() => {

View File

@@ -0,0 +1,48 @@
import { useNavigate } from "react-router-dom";
import { useState } from "react";
function Login(props) {
let navigate = useNavigate();
let [username, setUsername] = useState("");
let [password, setPassword] = useState("");
return (
<div>
<h1>Login</h1>
<label htmlFor="username"></label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<span>
<button
onClick={() => {
if (!username || !password) {
alert("Please enter username and password");
return;
}
props.setUser({ id: 123, username: username, password: password });
navigate("/");
}}
>
Login
</button>
<button
onClick={() => {
navigate("/register");
}}
>Register</button>
</span>
</div>
);
}
export default Login;

View File

@@ -1,9 +1,104 @@
import getFfmpegConfigListRespondExample from "../example-respond/get_ffmpeg_config_list.json";
function Manage() { function Manage() {
return ( return (
<div> <div className="page">
<h2>Manage</h2> <h2>Manage</h2>
</div> <h3>Server status</h3>
) <table>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Server status</td>
<td>
<span className="status-ok">OK</span>
</td>
</tr>
<tr>
<td>Server uptime</td>
<td>
<span>1 day, 23 hours, 59 minutes and 59 seconds</span>
</td>
</tr>
<tr>
<td>Server load</td>
<td>
<span>0.00 / 0.00 / 0.00</span>
</td>
</tr>
<tr>
<td>Server memory usage</td>
<td>
<span>0.00 MB</span>
</td>
</tr>
<tr>
<td>Server disk usage</td>
<td>
<span>0.00 MB</span>
</td>
</tr>
<tr>
<td>Server uptime</td>
<td>
<span>1 day, 23 hours, 59 minutes and 59 seconds</span>
</td>
</tr>
<tr>
<td>Server load</td>
<td>
<span>0.00 / 0.00 / 0.00</span>
</td>
</tr>
<tr>
<td>Server memory usage</td>
<td>
<span>0.00 MB</span>
</td>
</tr>
<tr>
<td>Server disk usage</td>
<td>
<span>0.00 MB</span>
</td>
</tr>
</tbody>
</table>
<h3>Database opeartions</h3>
<ul>
<li>.mp3</li>
<li>.flac</li>
<li>.wav</li>
<li>.ogg</li>
<li>.aac</li>
<li>.m4a</li>
</ul>
<input type="text" placeholder=".mp3" />
<button>Add Pattern</button>
<input type="text" placeholder="/path/to/root" />
<button>Import</button>
<h3>Ffmpeg Settings</h3>
<ol>
{getFfmpegConfigListRespondExample.ffmpeg_config_list.map(
(item, index) => (
<li>
{item.name} {item.args}
</li>
)
)}
</ol>
<span>
<input type="text" placeholder="name" />
<input type="text" placeholder="args" />
<button>Add</button>
</span>
</div>
);
} }
export default Manage; export default Manage;

View File

@@ -0,0 +1,37 @@
import { useParams, useNavigate } from "react-router-dom";
import ReviewEntry from "./ReviewEntry";
import SearchFiles from "./SearchFiles";
import getRandomFilesRespondExample from "../example-respond/get_random_files.json";
function Profile(props) {
let params = useParams();
let navigate = useNavigate();
return (
<div>
<h1>Profile of user {params.id}</h1>
{props.user && props.user.id === parseInt(params.id) && (
<button
onClick={() => {
props.setUser(null);
navigate("/");
}}
>
Logout
</button>
)}
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam
doloremque, quidem quisquam, quisquam quisquam quisquam quisquam
dignissimos.
</p>
<h3>Reviews</h3>
<ReviewEntry />
<ReviewEntry />
<h3>Liked music</h3>
<SearchFiles folder={{}} />
</div>
);
}
export default Profile;

View File

@@ -0,0 +1,51 @@
import { useNavigate } from "react-router-dom";
import { useState } from "react";
function Register(props) {
let navigate = useNavigate();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [password2, setPassword2] = useState("");
return (
<div>
<h1>Register</h1>
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<label htmlFor="password2">Confirm Password:</label>
<input
type="password"
id="password2"
value={password2}
onChange={(e) => setPassword2(e.target.value)}
/>
<button
onClick={() => {
if (!username || !password || !password2) {
alert("Please fill out all fields");
} else if (password !== password2) {
alert("Passwords do not match");
} else {
props.setUser({ id: 39, username: username, password: password });
navigate("/");
}
}}
>
Register
</button>
</div>
);
}
export default Register;

View File

@@ -0,0 +1,35 @@
import { useParams } from "react-router";
import { Link } from "react-router-dom";
import ReviewEntry from "./ReviewEntry";
function Review() {
let params = useParams();
return (
<div className="page">
<h3>Review on music ID {params.id}</h3>
<textarea
className="review-text"
placeholder="Write your review here"
></textarea>
<span>
<button>Submit</button>
<button>Add to fav</button>
</span>
<details open>
<summary>Liked by</summary>
<p>
<Link to="/profile/1">@User 1</Link>&nbsp;
<Link to="/profile/2">@User 2</Link>&nbsp;
<Link to="/profile/3">@User 3</Link>&nbsp;
<Link to="/profile/4">@User 4</Link>&nbsp;
</p>
</details>
<ReviewEntry />
<ReviewEntry />
<ReviewEntry />
</div>
);
}
export default Review;

View File

@@ -0,0 +1,18 @@
import { Link} from "react-router-dom";
function ReviewEntry() {
return (
<p>
<h5>
<Link to="/profile/2">@rin</Link> comment music ID 39 at
2019-01-01 12:23:45
</h5>
Agree with <Link to="/profile/1">@hmsy</Link>. I also like how well the
musician plays the guitar. They are all very good. They really make the
song sound better. I like the way the bass plays and the way the guitar
sounds. I like the way the drums sound.
</p>
);
}
export default ReviewEntry;

View File

@@ -1,5 +1,6 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import FilesTable from "./FilesTable"; import FilesTable from "./FilesTable";
import searchFilesRespondExample from "../example-respond/search_files.json"
function SearchFiles(props) { function SearchFiles(props) {
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
@@ -9,38 +10,7 @@ function SearchFiles(props) {
const limit = 10; const limit = 10;
function searchFiles() { function searchFiles() {
if ( setFiles(searchFilesRespondExample.files);
filename === "" &&
(props.folder === undefined || props.folder.id === undefined)
) {
return;
}
const folder = props.folder ? props.folder : {};
const url = folder.id
? "/api/v1/get_files_in_folder"
: "/api/v1/search_files";
setIsLoading(true);
fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
filename: filename,
limit: limit,
offset: offset,
folder_id: folder.id,
}),
})
.then((response) => response.json())
.then((data) => {
const files = data.files ? data.files : [];
setFiles(files);
})
.catch((error) => {
alert("get_files_in_folder error: " + error);
})
.finally(() => {
setIsLoading(false);
});
} }
function nextPage() { function nextPage() {

View File

@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
import FoldersTable from "./FoldersTable"; import FoldersTable from "./FoldersTable";
import SearchFiles from "./SearchFiles"; import SearchFiles from "./SearchFiles";
import searchFoldersRespondExample from "../example-respond/search_folders.json";
function SearchFolders(props) { function SearchFolders(props) {
const [foldername, setFoldername] = useState(""); const [foldername, setFoldername] = useState("");
@@ -12,35 +13,7 @@ function SearchFolders(props) {
const limit = 10; const limit = 10;
function searchFolder() { function searchFolder() {
if (foldername === "") { setFolders(searchFoldersRespondExample.folders);
return;
}
setIsLoading(true);
fetch("/api/v1/search_folders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
foldername: foldername,
limit: limit,
offset: offset,
}),
})
.then((response) => response.json())
.then((data) => {
let folders;
if (data.folders) {
folders = data.folders;
} else {
folders = [];
}
setFolders(folders);
})
.catch((error) => {
alert("search_folders error: " + error);
})
.finally(() => {
setIsLoading(false);
});
} }
function nextPage() { function nextPage() {

View File

@@ -1,25 +1,13 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
import FilesTable from "./FilesTable"; import FilesTable from "./FilesTable";
import GetFileInfoRespondExample from "../example-respond/get_file_info.json";
function Share(props) { function Share(props) {
let params = useParams(); let params = useParams();
const [file, setFile] = useState([]); const [file, setFile] = useState([]);
useEffect(() => { useEffect(() => {
fetch("/api/v1/get_file_info", { setFile([GetFileInfoRespondExample]);
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id: parseInt(params.id),
}),
})
.then((response) => response.json())
.then((data) => {
setFile([data]);
})
.catch((error) => {
alert("get_file_info error: " + error);
});
}, [params]); }, [params]);
return ( return (
<div className="page"> <div className="page">

23
web/src/component/User.js Normal file
View File

@@ -0,0 +1,23 @@
import { useNavigate } from "react-router";
function User(props) {
// props.user
// props.setUser
let navigate = useNavigate();
return (
<div
className="avatar"
onClick={() => {
if (props.user) {
navigate("/profile/" + props.user.id);
} else {
navigate("/login");
}
}}
>
{props.user ? props.user.username : "Login"}
</div>
);
}
export default User;

View File

@@ -0,0 +1,12 @@
{
"ffmpeg_config_list": [
{ "name": "OPUS 128k", "args": "-c:a libopus -ab 128k" },
{ "name": "OPUS 96k", "args": "-c:a libopus -ab 96k" },
{ "name": "OPUS 256k", "args": "-c:a libopus -ab 256k" },
{ "name": "OPUS 320k", "args": "-c:a libopus -ab 320k" },
{ "name": "OPUS 512k", "args": "-c:a libopus -ab 512k" },
{ "name": "AAC 128k", "args": "-c:a aac -ab 128k" },
{ "name": "AAC 256k", "args": "-c:a aac -ab 256k" },
{ "name": "全损音质 32k", "args": "-c:a libopus -ab 32k" }
]
}

View File

@@ -0,0 +1,7 @@
{
"id": 9856,
"folder_id": 898,
"foldername": "[2021.05.12] TVアニメ「シャドーハウス」EDテーマ「ないない」ReoNa [スペシャルエディション] [FLAC 96kHz24bit]",
"filename": "03. 生きてるだけでえらいよ.flac",
"filesize": 122761032
}

View File

@@ -0,0 +1,74 @@
{
"files": [
{
"id": 9727,
"folder_id": 958,
"foldername": "garnet (narry) — emerald [GARN-0002] (flac)",
"filename": "06. narry — malachite.flac",
"filesize": 28228112
},
{
"id": 4785,
"folder_id": 457,
"foldername": "Winter (FLAC)",
"filename": "08 - 恋.flac",
"filesize": 33576086
},
{
"id": 13943,
"folder_id": 1368,
"foldername": "[mikudb] 融合YELLOWS",
"filename": "10. カレーライスのうた.mp3",
"filesize": 2524925
},
{
"id": 21743,
"folder_id": 2207,
"foldername": "Tsumanne(^o^)",
"filename": "08.スーパートルコ行進曲 - オワタ\(^o^).mp3",
"filesize": 7677985
},
{
"id": 35918,
"folder_id": 3758,
"foldername": "2008 - Higurashi 2 - Ano hi, Ano Basho, Subete ni ' Arigatou",
"filename": "06 - Thanks (Bashee Arenge Version).flac",
"filesize": 39545977
},
{
"id": 32394,
"folder_id": 3341,
"foldername": "[彩音 xi-on] Eyes",
"filename": "08 - 少女さとり ~ 3rd Eye.mp3",
"filesize": 7538525
},
{
"id": 16934,
"folder_id": 1671,
"foldername": "Explorism",
"filename": "14.Please, My Producer (feat. SAK).mp3",
"filesize": 6099717
},
{
"id": 1381,
"folder_id": 131,
"foldername": "Garakuta Live (FLAC)",
"filename": "(09) [しけもく] 愛染エピローグ.flac",
"filesize": 25173829
},
{
"id": 18066,
"folder_id": 1791,
"foldername": "Jailbreak from the Sunday Morning!!",
"filename": "05. Good bye!! Melancory Sunday Morning!!.mp3",
"filesize": 10197778
},
{
"id": 41261,
"folder_id": 4305,
"foldername": "[KSLA-0124~6] Modification of Key Sounds Label [CD-FLAC]",
"filename": "3-01. Trigger (MUZIK SERVANT vs Freezer Remix).flac",
"filesize": 42772516
}
]
}

View File

@@ -0,0 +1,25 @@
{
"files": [
{
"id": 26555,
"folder_id": 2579,
"foldername": "[mikudb] SEB presents SUPER HATSUNE BEAT Vol. 1",
"filename": "02. White Letter (ゆうゆP Euro Arrange).mp3",
"filesize": 4252006
},
{
"id": 40891,
"folder_id": 4121,
"foldername": "初音ミクベスト memories",
"filename": "10.White letter.mp3",
"filesize": 6723982
},
{
"id": 43289,
"folder_id": 4384,
"foldername": "Hatsune Miku Best ~memories~",
"filename": "10.White Letter.flac",
"filesize": 20817678
}
]
}

View File

@@ -0,0 +1,32 @@
{
"folders": [
{
"id": 2037,
"foldername": "P∴Rhythmatiq — 七色リミックス [OSLA-0005] (flac+scans)"
},
{
"id": 2130,
"foldername": " P∴Rhythmatiq — P∴Rhythmatiq act09 [PRTQ-0017] (flac+scans)"
},
{
"id": 2176,
"foldername": "P∴Rhythmatiq — P∴Rhythmatiq act02 [PRTQ-0002] (flac)"
},
{
"id": 2184,
"foldername": "P∴Rhythmatiq — P∴Rhythmatiq act04 [PRTQ-0005] (flac)"
},
{
"id": 2190,
"foldername": "P∴Rhythmatiq — P∴Rhythmatiq Rock!! [PQPC-0001] (flac)"
},
{
"id": 2360,
"foldername": "P∴Rhythmatiq — P∴Rhythmatiq act11 [PRTQ-0025] (flac)"
},
{ "id": 3443, "foldername": "P∴Rhythmatiq EXTRA" },
{ "id": 3444, "foldername": "P∴Rhythmatiq Hearts" },
{ "id": 3445, "foldername": "P∴Rhythmatiq Re act" },
{ "id": 3446, "foldername": "P∴Rhythmatiq Rock!!" }
]
}