feat: add private directory

This commit is contained in:
Project_IO 2025-03-23 18:00:42 +09:00
parent 83a5ef9bf6
commit e4c6332398
9 changed files with 173 additions and 88 deletions

View file

@ -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())
}) })

View 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,
})
}

View file

@ -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) {

View file

@ -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 <></>;

View file

@ -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 <></>;

View file

@ -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);

View file

@ -50,6 +50,11 @@
} }
} }
.form {
display: flex;
min-width: 380px;
}
.line { .line {
margin-bottom: 15px; margin-bottom: 15px;
} }

View file

@ -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 {

View file

@ -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;