feat: add private directory
This commit is contained in:
parent
83a5ef9bf6
commit
e4c6332398
9 changed files with 173 additions and 88 deletions
|
@ -31,6 +31,11 @@ func New(app *gin.Engine, version *service.Version, apiOnly bool) {
|
||||||
auth.DELETE("/delete", deleteAcc)
|
auth.DELETE("/delete", deleteAcc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
privdir := api.Group("/privdir")
|
||||||
|
{
|
||||||
|
privdir.POST("/create", createDir)
|
||||||
|
}
|
||||||
|
|
||||||
api.GET("/version", func(ctx *gin.Context) {
|
api.GET("/version", func(ctx *gin.Context) {
|
||||||
ctx.String(200, "%s", version.String())
|
ctx.String(200, "%s", version.String())
|
||||||
})
|
})
|
||||||
|
|
60
internal/routes/privdir.go
Normal file
60
internal/routes/privdir.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.wh64.net/devproje/kuma-archive/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createDir(ctx *gin.Context) {
|
||||||
|
var err error
|
||||||
|
auth := service.NewAuthService()
|
||||||
|
username, password, ok := ctx.Request.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
ctx.JSON(401, gin.H{
|
||||||
|
"ok": 0,
|
||||||
|
"errno": "Unauthorized",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err = auth.VerifyToken(username, password); !ok {
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(401, gin.H{
|
||||||
|
"ok": 0,
|
||||||
|
"errno": "Unauthorized",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc *service.Account
|
||||||
|
acc, err = auth.Read(username)
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(500, gin.H{
|
||||||
|
"ok": 0,
|
||||||
|
"errno": "Interval Server Error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path := ctx.PostForm("path")
|
||||||
|
privdir := service.NewPrivDirService(acc)
|
||||||
|
|
||||||
|
var id string
|
||||||
|
if id, err = privdir.Create(path); err != nil {
|
||||||
|
ctx.JSON(500, gin.H{
|
||||||
|
"ok": 0,
|
||||||
|
"errno": fmt.Sprintf("'%s' directory is already registered", path),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(200, gin.H{
|
||||||
|
"ok": 1,
|
||||||
|
"dir_id": id,
|
||||||
|
})
|
||||||
|
}
|
|
@ -60,22 +60,22 @@ func NewPrivDirService(acc *Account) *PrivDirService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *PrivDirService) Create(dirname string) error {
|
func (sv *PrivDirService) Create(dirname string) (string, error) {
|
||||||
db, err := Open()
|
db, err := Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
id := uuid.NewString()
|
id := uuid.NewString()
|
||||||
stmt, err := db.Prepare("insert into PrivDir(id, dirname, owner) values (?, ?, ?);")
|
stmt, err := db.Prepare("insert into PrivDir(id, dirname, owner) values (?, ?, ?);")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
defer stmt.Close()
|
defer stmt.Close()
|
||||||
|
|
||||||
_, err = stmt.Exec(id, dirname, sv.acc.Username)
|
_, err = stmt.Exec(id, dirname, sv.acc.Username)
|
||||||
return nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *PrivDirService) Read(dirname string) (*PrivDir, error) {
|
func (sv *PrivDirService) Read(dirname string) (*PrivDir, error) {
|
||||||
|
|
12
src/App.tsx
12
src/App.tsx
|
@ -1,6 +1,6 @@
|
||||||
import Login from "./components/login";
|
import Login from "./components/login";
|
||||||
import Logout from "./components/logout";
|
import Logout from "./components/logout";
|
||||||
import { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Settings from "./components/settings";
|
import Settings from "./components/settings";
|
||||||
import { useVersion } from "./store/version";
|
import { useVersion } from "./store/version";
|
||||||
import NotFound from "./components/notfound";
|
import NotFound from "./components/notfound";
|
||||||
|
@ -39,18 +39,20 @@ function Dashboard({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
function View() {
|
function View() {
|
||||||
const path = usePath();
|
const path = usePath();
|
||||||
|
const auth = useAuthStore();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [load, setLoad] = useState(false);
|
const [load, setLoad] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!load) {
|
if (!load) {
|
||||||
path.update(location.pathname.substring(1, location.pathname.length)).then(() => {
|
path.update(location.pathname.substring(1, location.pathname.length), auth.token)
|
||||||
setLoad(true);
|
.then(() => {
|
||||||
});
|
setLoad(true);
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, [load, path, location]);
|
}, [auth, load, path, location]);
|
||||||
|
|
||||||
if (!load) {
|
if (!load) {
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|
|
@ -6,88 +6,91 @@ import { DynamicIcon, IconName } from "lucide-react/dynamic";
|
||||||
|
|
||||||
import "./fview.scss";
|
import "./fview.scss";
|
||||||
import { FileNavigator } from "../navigation";
|
import { FileNavigator } from "../navigation";
|
||||||
|
import {useAuthStore} from "../../store/auth.ts";
|
||||||
|
|
||||||
function FileView() {
|
function FileView() {
|
||||||
const path = usePath();
|
const path = usePath();
|
||||||
|
const auth = useAuthStore();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [load, setLoad] = useState(false);
|
const [load, setLoad] = useState(false);
|
||||||
const [type, setType] = useState<IconName>("file");
|
const [type, setType] = useState<IconName>("file");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!load) {
|
if (!load) {
|
||||||
path.update(location.pathname.substring(1, location.pathname.length)).then(() => {
|
path.update(location.pathname.substring(1, location.pathname.length), auth.token)
|
||||||
setLoad(true);
|
.then(() => {
|
||||||
|
setLoad(true);
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case path.data?.path.endsWith(".zip"):
|
case path.data?.path.endsWith(".zip"):
|
||||||
case path.data?.path.endsWith(".tar"):
|
case path.data?.path.endsWith(".tar"):
|
||||||
case path.data?.path.endsWith(".tar.gz"):
|
case path.data?.path.endsWith(".tar.gz"):
|
||||||
case path.data?.path.endsWith(".tar.xz"):
|
case path.data?.path.endsWith(".tar.xz"):
|
||||||
case path.data?.path.endsWith(".7z"):
|
case path.data?.path.endsWith(".7z"):
|
||||||
case path.data?.path.endsWith(".rar"):
|
case path.data?.path.endsWith(".rar"):
|
||||||
setType("file-archive");
|
setType("file-archive");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".pdf"):
|
case path.data?.path.endsWith(".pdf"):
|
||||||
setType("file-pen-line");
|
setType("file-pen-line");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".doc"):
|
case path.data?.path.endsWith(".doc"):
|
||||||
case path.data?.path.endsWith(".docx"):
|
case path.data?.path.endsWith(".docx"):
|
||||||
setType("file-chart-pie");
|
setType("file-chart-pie");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".xls"):
|
case path.data?.path.endsWith(".xls"):
|
||||||
case path.data?.path.endsWith(".xlsx"):
|
case path.data?.path.endsWith(".xlsx"):
|
||||||
setType("file-spreadsheet");
|
setType("file-spreadsheet");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".ppt"):
|
case path.data?.path.endsWith(".ppt"):
|
||||||
case path.data?.path.endsWith(".pptx"):
|
case path.data?.path.endsWith(".pptx"):
|
||||||
setType("file-sliders");
|
setType("file-sliders");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".jpg"):
|
case path.data?.path.endsWith(".jpg"):
|
||||||
case path.data?.path.endsWith(".jpeg"):
|
case path.data?.path.endsWith(".jpeg"):
|
||||||
case path.data?.path.endsWith(".png"):
|
case path.data?.path.endsWith(".png"):
|
||||||
case path.data?.path.endsWith(".gif"):
|
case path.data?.path.endsWith(".gif"):
|
||||||
setType("file-image");
|
setType("file-image");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".mp3"):
|
case path.data?.path.endsWith(".mp3"):
|
||||||
case path.data?.path.endsWith(".wav"):
|
case path.data?.path.endsWith(".wav"):
|
||||||
setType("file-audio");
|
setType("file-audio");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".mp4"):
|
case path.data?.path.endsWith(".mp4"):
|
||||||
case path.data?.path.endsWith(".mkv"):
|
case path.data?.path.endsWith(".mkv"):
|
||||||
setType("file-video");
|
setType("file-video");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".c"):
|
case path.data?.path.endsWith(".c"):
|
||||||
case path.data?.path.endsWith(".cpp"):
|
case path.data?.path.endsWith(".cpp"):
|
||||||
case path.data?.path.endsWith(".js"):
|
case path.data?.path.endsWith(".js"):
|
||||||
case path.data?.path.endsWith(".ts"):
|
case path.data?.path.endsWith(".ts"):
|
||||||
case path.data?.path.endsWith(".jsx"):
|
case path.data?.path.endsWith(".jsx"):
|
||||||
case path.data?.path.endsWith(".tsx"):
|
case path.data?.path.endsWith(".tsx"):
|
||||||
case path.data?.path.endsWith(".py"):
|
case path.data?.path.endsWith(".py"):
|
||||||
case path.data?.path.endsWith(".java"):
|
case path.data?.path.endsWith(".java"):
|
||||||
case path.data?.path.endsWith(".rb"):
|
case path.data?.path.endsWith(".rb"):
|
||||||
case path.data?.path.endsWith(".go"):
|
case path.data?.path.endsWith(".go"):
|
||||||
case path.data?.path.endsWith(".rs"):
|
case path.data?.path.endsWith(".rs"):
|
||||||
case path.data?.path.endsWith(".php"):
|
case path.data?.path.endsWith(".php"):
|
||||||
case path.data?.path.endsWith(".html"):
|
case path.data?.path.endsWith(".html"):
|
||||||
case path.data?.path.endsWith(".css"):
|
case path.data?.path.endsWith(".css"):
|
||||||
case path.data?.path.endsWith(".scss"):
|
case path.data?.path.endsWith(".scss"):
|
||||||
setType("file-code");
|
setType("file-code");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".sh"):
|
case path.data?.path.endsWith(".sh"):
|
||||||
case path.data?.path.endsWith(".bat"):
|
case path.data?.path.endsWith(".bat"):
|
||||||
setType("file-terminal");
|
setType("file-terminal");
|
||||||
break;
|
break;
|
||||||
case path.data?.path.endsWith(".json"):
|
case path.data?.path.endsWith(".json"):
|
||||||
setType("file-json");
|
setType("file-json");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setType("file");
|
setType("file");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, [path, location, load]);
|
}, [auth, path, location, load]);
|
||||||
|
|
||||||
if (typeof path.data === "undefined")
|
if (typeof path.data === "undefined")
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|
|
@ -10,13 +10,13 @@ function Settings() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (auth.token === null) {
|
if (auth.token === null) {
|
||||||
// document.location.href = "/";
|
document.location.href = "/login";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auth.checkToken(auth.token).then((ok) => {
|
auth.checkToken(auth.token).then((ok) => {
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// document.location.href = "/";
|
document.location.href = "/login";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ function AccountSetting({ auth }: { auth: AuthState }) {
|
||||||
<span>If you change your password, you will need to log in again.</span>
|
<span>If you change your password, you will need to log in again.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form className="box-col" id="pw-change">
|
<form className="box-col form" id="pw-change">
|
||||||
<PasswordInput placeholder="Password" ref={orRef} />
|
<PasswordInput placeholder="Password" ref={orRef} />
|
||||||
<PasswordInput placeholder="New Password" ref={pwRef} />
|
<PasswordInput placeholder="New Password" ref={pwRef} />
|
||||||
<PasswordInput placeholder="Check Password" ref={ckRef} />
|
<PasswordInput placeholder="Check Password" ref={ckRef} />
|
||||||
|
@ -122,7 +122,7 @@ function AccountSetting({ auth }: { auth: AuthState }) {
|
||||||
<span>You can delete account. This action is irreversible. Please proceed with caution.</span>
|
<span>You can delete account. This action is irreversible. Please proceed with caution.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form className="box-col">
|
<form className="box-col form">
|
||||||
<label className="checkbox">
|
<label className="checkbox">
|
||||||
<input type="checkbox" onChange={ev => {
|
<input type="checkbox" onChange={ev => {
|
||||||
setRemove(ev.target.checked);
|
setRemove(ev.target.checked);
|
||||||
|
|
|
@ -50,6 +50,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
min-width: 380px;
|
||||||
|
}
|
||||||
|
|
||||||
.line {
|
.line {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,12 +32,13 @@
|
||||||
--btn-success-focus: #1d7c33;
|
--btn-success-focus: #1d7c33;
|
||||||
--btn-danger-focus: #a72532;
|
--btn-danger-focus: #a72532;
|
||||||
|
|
||||||
--font-family: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
|
--font-family: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
--font-mono: "JetBrains Mono", monospace;
|
--font-mono: "JetBrains Mono", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
||||||
|
@ -60,7 +61,8 @@ html, body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
font-family: var(--font-family);
|
font-family: var(--font-family), sans-serif;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { create } from "zustand";
|
||||||
|
|
||||||
interface PathState {
|
interface PathState {
|
||||||
data: PathResponse | undefined;
|
data: PathResponse | undefined;
|
||||||
update(path: string): Promise<void>;
|
update(path: string, token: string | null): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PathResponse {
|
interface PathResponse {
|
||||||
|
@ -23,8 +23,16 @@ export interface DirEntry {
|
||||||
|
|
||||||
export const usePath = create<PathState>((set) => ({
|
export const usePath = create<PathState>((set) => ({
|
||||||
data: undefined,
|
data: undefined,
|
||||||
update: async (path: string) => {
|
update: async (path: string, token: string | null) => {
|
||||||
const res = await fetch(`/api/worker/discover/${path}`);
|
const res = await fetch(`/api/worker/discover/${path}`, {
|
||||||
|
headers: {
|
||||||
|
"Authorization": token === null ? "" : `Basic ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
document.location.href = "/login";
|
||||||
|
}
|
||||||
|
|
||||||
if (res.status !== 200 && res.status !== 304) {
|
if (res.status !== 200 && res.status !== 304) {
|
||||||
set({ data: undefined });
|
set({ data: undefined });
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in a new issue