Merge branch 'master' into dbms
This commit is contained in:
@@ -411,10 +411,10 @@ Anonymous API can be called by anonymous.
|
||||
|
||||
Currently only few APIs in font-end.
|
||||
|
||||
- `/#/share/39`
|
||||
- `/#/files/39/share`
|
||||
|
||||
Share a specific file.
|
||||
|
||||
- `/#/search-folders/2614`
|
||||
- `/#/folders/2614`
|
||||
|
||||
Show files in a specific folder.
|
||||
|
||||
@@ -188,6 +188,15 @@ func (database *Database) FindFolder(folder string) (int64, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (database *Database) FindFile(folderId int64, filename string) (int64, error) {
|
||||
var id int64
|
||||
err := database.stmt.findFile.QueryRow(folderId, filename).Scan(&id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (database *Database) InsertFolder(folder string) (int64, error) {
|
||||
result, err := database.stmt.insertFolder.Exec(folder, filepath.Base(folder))
|
||||
if err != nil {
|
||||
@@ -217,6 +226,13 @@ func (database *Database) Insert(path string, filesize int64) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if file exists, skip it
|
||||
_, err = database.FindFile(folderId, filename)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = database.InsertFile(folderId, filename, filesize)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -21,7 +21,8 @@ var initFoldersTableQuery = `CREATE TABLE IF NOT EXISTS folders (
|
||||
var initFeedbacksTableQuery = `CREATE TABLE IF NOT EXISTS feedbacks (
|
||||
id INTEGER PRIMARY KEY,
|
||||
time INTEGER NOT NULL,
|
||||
feedback TEXT NOT NULL
|
||||
feedback TEXT NOT NULL,
|
||||
header TEXT NOT NULL
|
||||
);`
|
||||
|
||||
var initUsersTableQuery = `CREATE TABLE IF NOT EXISTS users (
|
||||
@@ -104,6 +105,8 @@ VALUES (?, ?);`
|
||||
|
||||
var findFolderQuery = `SELECT id FROM folders WHERE folder = ? LIMIT 1;`
|
||||
|
||||
var findFileQuery = `SELECT id FROM files WHERE folder_id = ? AND filename = ? LIMIT 1;`
|
||||
|
||||
var insertFileQuery = `INSERT INTO files (folder_id, filename, filesize)
|
||||
VALUES (?, ?, ?);`
|
||||
|
||||
@@ -147,8 +150,8 @@ JOIN folders ON files.folder_id = folders.id
|
||||
ORDER BY RANDOM()
|
||||
LIMIT ?;`
|
||||
|
||||
var insertFeedbackQuery = `INSERT INTO feedbacks (time, feedback)
|
||||
VALUES (?, ?);`
|
||||
var insertFeedbackQuery = `INSERT INTO feedbacks (time, feedback, header)
|
||||
VALUES (?, ?, ?);`
|
||||
|
||||
type Stmt struct {
|
||||
initFilesTable *sql.Stmt
|
||||
@@ -166,6 +169,7 @@ type Stmt struct {
|
||||
insertFolder *sql.Stmt
|
||||
insertFile *sql.Stmt
|
||||
findFolder *sql.Stmt
|
||||
findFile *sql.Stmt
|
||||
searchFiles *sql.Stmt
|
||||
getFolder *sql.Stmt
|
||||
dropFiles *sql.Stmt
|
||||
@@ -316,6 +320,12 @@ func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// init findFile statement
|
||||
stmt.findFile, err = sqlConn.Prepare(findFileQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// init insertFile stmt
|
||||
stmt.insertFile, err = sqlConn.Prepare(insertFileQuery)
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import "./App.css";
|
||||
import GetRandomFiles from "./component/GetRandomFiles";
|
||||
import SearchFiles from "./component/SearchFiles";
|
||||
import SearchFolders from "./component/SearchFolders";
|
||||
import FilesInFolder from "./component/FilesInFolder";
|
||||
import Manage from "./component/Manage";
|
||||
import Share from "./component/Share";
|
||||
import AudioPlayer from "./component/AudioPlayer";
|
||||
@@ -28,10 +29,10 @@ function App() {
|
||||
<NavLink to="/" className="nav-link">
|
||||
Feeling luckly
|
||||
</NavLink>
|
||||
<NavLink to="/search-files" className="nav-link">
|
||||
<NavLink to="/files" className="nav-link">
|
||||
Files
|
||||
</NavLink>
|
||||
<NavLink to="/search-folders" className="nav-link">
|
||||
<NavLink to="/folders" className="nav-link">
|
||||
Folders
|
||||
</NavLink>
|
||||
<NavLink to="/manage" className="nav-link">
|
||||
@@ -47,20 +48,20 @@ function App() {
|
||||
element={<GetRandomFiles setPlayingFile={setPlayingFile} />}
|
||||
/>
|
||||
<Route
|
||||
path="/search-files"
|
||||
path="/files"
|
||||
element={<SearchFiles setPlayingFile={setPlayingFile} />}
|
||||
/>
|
||||
<Route
|
||||
path="/search-folders"
|
||||
path="/folders"
|
||||
element={<SearchFolders setPlayingFile={setPlayingFile} />}
|
||||
/>
|
||||
<Route
|
||||
path="/search-folders/:id"
|
||||
element={<SearchFolders setPlayingFile={setPlayingFile} />}
|
||||
path="/folders/:id"
|
||||
element={<FilesInFolder setPlayingFile={setPlayingFile} />}
|
||||
/>
|
||||
<Route path="/manage" element={<Manage />} />
|
||||
<Route
|
||||
path="/share/:id"
|
||||
path="/files/:id/share"
|
||||
element={<Share setPlayingFile={setPlayingFile} />}
|
||||
/>
|
||||
</Routes>
|
||||
|
||||
@@ -80,7 +80,7 @@ function AudioPlayer(props) {
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
navigate(`search-folders/${props.playingFile.folder_id}`)
|
||||
navigate(`/folders/${props.playingFile.folder_id}`)
|
||||
}
|
||||
>
|
||||
{props.playingFile.foldername}
|
||||
|
||||
@@ -33,7 +33,7 @@ function FileDialog(props) {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate(`/share/${props.file.id}`);
|
||||
navigate(`/files/${props.file.id}/share`);
|
||||
props.setShowStatus(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -25,7 +25,7 @@ function FileEntry(props) {
|
||||
</td>
|
||||
<td
|
||||
className="clickable"
|
||||
onClick={() => navigate(`/search-folders/${props.file.folder_id}`)}
|
||||
onClick={() => navigate(`/folders/${props.file.folder_id}`)}
|
||||
>
|
||||
{props.file.foldername}
|
||||
</td>
|
||||
|
||||
60
web/src/component/FilesInFolder.js
Normal file
60
web/src/component/FilesInFolder.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { useParams } from "react-router";
|
||||
import { useState, useEffect } from "react";
|
||||
import FilesTable from "./FilesTable";
|
||||
|
||||
function FilesInFolder(props) {
|
||||
let params = useParams();
|
||||
const [files, setFiles] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [offset, setOffset] = useState(0);
|
||||
const limit = 10;
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
fetch("/api/v1/get_files_in_folder", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
folder_id: parseInt(params.id),
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setFiles(data.files ? data.files : []);
|
||||
})
|
||||
.catch((error) => alert(error))
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [params.id, offset]);
|
||||
|
||||
function nextPage() {
|
||||
setOffset(offset + limit);
|
||||
}
|
||||
|
||||
function lastPage() {
|
||||
const offsetValue = offset - limit;
|
||||
if (offsetValue < 0) {
|
||||
return;
|
||||
}
|
||||
setOffset(offsetValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<h3>Files in Folder</h3>
|
||||
<div className="search_toolbar">
|
||||
<button onClick={lastPage}>Last page</button>
|
||||
<button disabled>
|
||||
{isLoading ? "Loading..." : `${offset} - ${offset + files.length}`}
|
||||
</button>
|
||||
<button onClick={nextPage}>Next page</button>
|
||||
</div>
|
||||
<FilesTable setPlayingFile={props.setPlayingFile} files={files} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilesInFolder;
|
||||
@@ -1,6 +1,9 @@
|
||||
import FileEntry from "./FileEntry";
|
||||
|
||||
function FilesTable(props) {
|
||||
if (props.files.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
@@ -2,6 +2,9 @@ import { useNavigate } from "react-router";
|
||||
|
||||
function FoldersTable(props) {
|
||||
let navigate = useNavigate();
|
||||
if (props.folders.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
@@ -14,12 +17,12 @@ function FoldersTable(props) {
|
||||
{props.folders.map((folder) => (
|
||||
<tr key={folder.id}>
|
||||
<td
|
||||
onClick={() => navigate(`/search-folders/${folder.id}`)}
|
||||
onClick={() => navigate(`/folders/${folder.id}`)}
|
||||
className="clickable"
|
||||
>
|
||||
{folder.foldername}
|
||||
</td>
|
||||
<td onClick={() => navigate(`/search-folders/${folder.id}`)}>
|
||||
<td onClick={() => navigate(`/folders/${folder.id}`)}>
|
||||
<button>View</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,9 +1,51 @@
|
||||
import { useState } from "react";
|
||||
|
||||
function Manage() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Manage</h2>
|
||||
</div>
|
||||
)
|
||||
const [token, setToken] = useState("");
|
||||
const [walkPath, setWalkPath] = useState("");
|
||||
|
||||
function updateDatabase() {
|
||||
fetch("/api/v1/walk", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: token,
|
||||
root: walkPath,
|
||||
pattern: [".wav", ".mp3"],
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Manage</h2>
|
||||
<input
|
||||
type="text"
|
||||
value={token}
|
||||
placeholder="token"
|
||||
onChange={(e) => setToken(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={walkPath}
|
||||
placeholder="walk path"
|
||||
onChange={(e) => setWalkPath(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
updateDatabase();
|
||||
}}
|
||||
>
|
||||
Update Database
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Manage;
|
||||
|
||||
@@ -9,25 +9,14 @@ function SearchFiles(props) {
|
||||
const limit = 10;
|
||||
|
||||
function searchFiles() {
|
||||
if (
|
||||
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, {
|
||||
fetch("/api/v1/search_files", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
filename: filename,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
folder_id: folder.id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
@@ -36,7 +25,7 @@ function SearchFiles(props) {
|
||||
setFiles(files);
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("get_files_in_folder error: " + error);
|
||||
alert("search_files error: " + error);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
@@ -55,35 +44,29 @@ function SearchFiles(props) {
|
||||
setOffset(offsetValue);
|
||||
}
|
||||
|
||||
useEffect(() => searchFiles(), [offset, props.folder]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
useEffect(() => searchFiles(), [offset]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<h3>Search Files</h3>
|
||||
<div className="search_toolbar">
|
||||
{!props.folder && (
|
||||
<input
|
||||
onChange={(event) => setFilename(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
searchFiles();
|
||||
}
|
||||
}}
|
||||
type="text"
|
||||
placeholder="Enter filename"
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
onChange={(event) => setFilename(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
searchFiles();
|
||||
}
|
||||
}}
|
||||
type="text"
|
||||
placeholder="Enter filename"
|
||||
/>
|
||||
<button
|
||||
disabled={!!props.folder}
|
||||
onClick={() => {
|
||||
searchFiles();
|
||||
}}
|
||||
>
|
||||
{isLoading ? "Loading..." : "Search"}
|
||||
</button>
|
||||
{props.folder && props.folder.foldername && (
|
||||
<button onClick={searchFiles}>{props.folder.foldername}</button>
|
||||
)}
|
||||
<button onClick={lastPage}>Last page</button>
|
||||
<button disabled>
|
||||
{offset} - {offset + files.length}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import FoldersTable from "./FoldersTable";
|
||||
import SearchFiles from "./SearchFiles";
|
||||
|
||||
function SearchFolders(props) {
|
||||
function SearchFolders() {
|
||||
const [foldername, setFoldername] = useState("");
|
||||
const [folders, setFolders] = useState([]);
|
||||
const [folder, setFolder] = useState({});
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const limit = 10;
|
||||
@@ -27,13 +24,7 @@ function SearchFolders(props) {
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
let folders;
|
||||
if (data.folders) {
|
||||
folders = data.folders;
|
||||
} else {
|
||||
folders = [];
|
||||
}
|
||||
setFolders(folders);
|
||||
setFolders(data.folders ? data.folders : []);
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("search_folders error: " + error);
|
||||
@@ -55,17 +46,7 @@ function SearchFolders(props) {
|
||||
setOffset(offsetValue);
|
||||
}
|
||||
|
||||
function viewFolder(folder) {
|
||||
setFolder(folder);
|
||||
}
|
||||
|
||||
let params = useParams();
|
||||
useEffect(() => searchFolder(), [offset]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
if (params.id !== undefined) {
|
||||
setFolder({ id: parseInt(params.id) });
|
||||
}
|
||||
}, [params.id]);
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
@@ -90,8 +71,7 @@ function SearchFolders(props) {
|
||||
</button>
|
||||
<button onClick={nextPage}>Next page</button>
|
||||
</div>
|
||||
<FoldersTable viewFolder={viewFolder} folders={folders} />
|
||||
<SearchFiles setPlayingFile={props.setPlayingFile} folder={folder} />
|
||||
<FoldersTable folders={folders} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user