Quasi finito
This commit is contained in:
@@ -7,6 +7,8 @@ import { AdminPanel } from '../pages/Admin.jsx'
|
|||||||
import { ApiKeys } from '../pages/ApiKeys.jsx'
|
import { ApiKeys } from '../pages/ApiKeys.jsx'
|
||||||
import { Login } from '../pages/Login.jsx'
|
import { Login } from '../pages/Login.jsx'
|
||||||
import { Dashboard } from '../pages/Dashboard.jsx'
|
import { Dashboard } from '../pages/Dashboard.jsx'
|
||||||
|
import { Buckets } from '../pages/Buckets.jsx'
|
||||||
|
import { Bucket } from '../pages/Bucket.jsx'
|
||||||
|
|
||||||
export const MainContent = () => {
|
export const MainContent = () => {
|
||||||
const { setSidebarVisible, route, user } = useStore()
|
const { setSidebarVisible, route, user } = useStore()
|
||||||
@@ -17,6 +19,8 @@ export const MainContent = () => {
|
|||||||
login: Login,
|
login: Login,
|
||||||
admin: AdminPanel,
|
admin: AdminPanel,
|
||||||
apiKeys: ApiKeys,
|
apiKeys: ApiKeys,
|
||||||
|
buckets: Buckets,
|
||||||
|
bucket: Bucket,
|
||||||
}[route.id] || (() => <>Still nothing here</>)
|
}[route.id] || (() => <>Still nothing here</>)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ const App = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (id === 'unknown') {
|
||||||
|
location.href = '/'
|
||||||
|
}
|
||||||
|
|
||||||
const user = useUser(user => {
|
const user = useUser(user => {
|
||||||
if (id !== 'login' && user !== 'admin') {
|
if (id !== 'login' && user !== 'admin') {
|
||||||
location.href = '/login'
|
location.href = '/login'
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||||
|
import { Icon } from '../components/Icon.jsx'
|
||||||
|
import { useStore } from '../context.jsx'
|
||||||
|
import { useRemoteState } from '../util.jsx'
|
||||||
|
|
||||||
|
export const Bucket = () => {
|
||||||
|
const {
|
||||||
|
route: {
|
||||||
|
params: { bucket },
|
||||||
|
},
|
||||||
|
showToast,
|
||||||
|
} = useStore()
|
||||||
|
|
||||||
|
const [bucketObjects, updateBucketObjects] = useRemoteState(`/api/buckets/${bucket}`, [])
|
||||||
|
|
||||||
|
const fileInputRef = useRef(null)
|
||||||
|
|
||||||
|
const uploadFile = async () => {
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
formData.append('file', fileInputRef.current.files[0])
|
||||||
|
|
||||||
|
showToast(<>Sto caricando il file...</>)
|
||||||
|
|
||||||
|
const res = await fetch(`/api/buckets/${bucket}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const { id } = await res.json()
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
<>
|
||||||
|
File caricato con id <code>{id}</code>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
await updateBucketObjects()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteObject = async id => {
|
||||||
|
const res = await fetch(`/api/buckets/${bucket}/${id}`, { method: 'DELETE' })
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast('Oggetto rimosso')
|
||||||
|
} else {
|
||||||
|
showToast(`Errore: ${await res.text()}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="row">
|
||||||
|
<div class="row-group">
|
||||||
|
<div class="title">
|
||||||
|
Bucket / <code>{bucket}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row-group">
|
||||||
|
<button class="icon" onClick={() => fileInputRef.current.click()}>
|
||||||
|
<Icon name="file_upload" />
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={fileInputRef}
|
||||||
|
onChange={() => uploadFile()}
|
||||||
|
style="display: none;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table">
|
||||||
|
<div class="cells">
|
||||||
|
{/* Header */}
|
||||||
|
<div class="header">Objects</div>
|
||||||
|
<div class="header">Actions</div>
|
||||||
|
{/* Rows */}
|
||||||
|
{bucketObjects.map(key => (
|
||||||
|
<>
|
||||||
|
<div class="name">
|
||||||
|
<code>{key}</code>
|
||||||
|
</div>
|
||||||
|
<div class="options">
|
||||||
|
<a
|
||||||
|
class="button icon flat"
|
||||||
|
href={`/api/buckets/${bucket}/${key}`}
|
||||||
|
>
|
||||||
|
<Icon name="file_download" />
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
class="icon delete flat"
|
||||||
|
onClick={() => deleteObject(key)}
|
||||||
|
>
|
||||||
|
<Icon name="delete" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
|
import { Icon } from '../components/Icon.jsx'
|
||||||
|
import { showToast } from '../components/Toasts.jsx'
|
||||||
|
import { useRemoteState } from '../util.jsx'
|
||||||
|
|
||||||
|
export const Buckets = () => {
|
||||||
|
const [buckets, updateBuckets] = useRemoteState('/api/buckets', [])
|
||||||
|
const [allBucketSettings, setAllBucketSettings] = useState({})
|
||||||
|
|
||||||
|
const updateBucketSettings = async bucket => {
|
||||||
|
const res = await fetch(`/api/buckets/${bucket}/settings`)
|
||||||
|
const value = await res.json()
|
||||||
|
|
||||||
|
setAllBucketSettings(s => ({ ...s, [bucket]: value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
for (const bucket of buckets) {
|
||||||
|
updateBucketSettings(bucket)
|
||||||
|
}
|
||||||
|
}, [buckets])
|
||||||
|
|
||||||
|
const [newBucketName, setNewBucketName] = useState('')
|
||||||
|
const [newBucketSettings, setNewBucketSettings] = useState('')
|
||||||
|
|
||||||
|
const createBucket = async () => {
|
||||||
|
const { path } = JSON.parse(newBucketSettings || '{"path":""}')
|
||||||
|
|
||||||
|
const res = await fetch(`/api/buckets/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
bucket: newBucketName,
|
||||||
|
path,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
await updateBuckets()
|
||||||
|
} else {
|
||||||
|
showToast(`Errore: ${await res.text()}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="row">
|
||||||
|
<div class="row-group">
|
||||||
|
<div class="title">Buckets</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table">
|
||||||
|
<div class="cells">
|
||||||
|
{/* Header */}
|
||||||
|
<div class="header">Bucket</div>
|
||||||
|
<div class="header">Settings</div>
|
||||||
|
<div class="header">Options</div>
|
||||||
|
{/* Rows */}
|
||||||
|
{buckets.map(bucket => (
|
||||||
|
<>
|
||||||
|
<div class="name">
|
||||||
|
<a href={`/buckets/${bucket}`}>
|
||||||
|
<code>{bucket}</code>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="settings">
|
||||||
|
<input
|
||||||
|
readonly
|
||||||
|
class="mono"
|
||||||
|
type="text"
|
||||||
|
value={JSON.stringify(allBucketSettings[bucket])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="options">
|
||||||
|
<button class="icon delete flat">
|
||||||
|
<Icon name="delete" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="/api/buckets" method="POST">
|
||||||
|
<div class="fill title">Nuovo Bucket</div>
|
||||||
|
|
||||||
|
<label for="new-bucket-name">Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="bucket"
|
||||||
|
id="new-bucket-name"
|
||||||
|
placeholder="new-bucket"
|
||||||
|
value={newBucketName}
|
||||||
|
onInput={e => setNewBucketName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<label for="new-bucket-settings">Settings</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="settings"
|
||||||
|
id="new-bucket-settings"
|
||||||
|
placeholder={`{"path":"${newBucketName || 'new-bucket'}/"}`}
|
||||||
|
value={newBucketSettings}
|
||||||
|
onInput={e => setNewBucketSettings(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="fill center">
|
||||||
|
<button type="button" onClick={() => createBucket()}>
|
||||||
|
Crea Bucket
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
+28
-1
@@ -33,8 +33,14 @@ export function useUser(onLoginStateChanged) {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Hooks
|
||||||
|
//
|
||||||
|
|
||||||
|
// Router
|
||||||
|
|
||||||
function matchPattern(pattern, url) {
|
function matchPattern(pattern, url) {
|
||||||
const r = `^${pattern.replace(/:([a-zA-Z0-9\_\-]+)/g, '(?<$1>.+?)')}$`
|
const r = `^${pattern.replace(/:([a-zA-Z0-9\_\-]+)/g, '(?<$1>[^\\/\\?]+?)')}$`
|
||||||
return new RegExp(r).exec(url)
|
return new RegExp(r).exec(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +55,27 @@ export const useRouter = routes => {
|
|||||||
return ['unknown', { id: 'unknown' }, {}]
|
return ['unknown', { id: 'unknown' }, {}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// useRemoteState
|
||||||
|
|
||||||
|
export function useRemoteState(url, initialValue = null) {
|
||||||
|
const [value, setValue] = useState(initialValue)
|
||||||
|
|
||||||
|
const updateValue = async () => {
|
||||||
|
const res = await fetch(url)
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error(res.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(await res.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateValue()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return [value, updateValue]
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Change Case Utility
|
// Change Case Utility
|
||||||
//
|
//
|
||||||
|
|||||||
+82
-36
@@ -13,6 +13,7 @@
|
|||||||
--bg-dark-400: #55555f;
|
--bg-dark-400: #55555f;
|
||||||
--bg-dark-500: #38383d;
|
--bg-dark-500: #38383d;
|
||||||
|
|
||||||
|
--fg-light-300: #5d5d5d;
|
||||||
--fg-light-400: #3d3d3d;
|
--fg-light-400: #3d3d3d;
|
||||||
--fg-light-500: #333;
|
--fg-light-500: #333;
|
||||||
|
|
||||||
@@ -215,6 +216,8 @@ main {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
padding: 1rem 1rem 0 1rem;
|
padding: 1rem 1rem 0 1rem;
|
||||||
@@ -265,6 +268,43 @@ main {
|
|||||||
// Components
|
// Components
|
||||||
//
|
//
|
||||||
|
|
||||||
|
.toasts {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
bottom: 1rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
transition: all 150ms linear;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
background: #38383d;
|
||||||
|
color: var(--fg-dark-500);
|
||||||
|
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
|
opacity: 0.95;
|
||||||
|
|
||||||
|
animation: toast-fade-in 250ms ease-out;
|
||||||
|
transition: opacity 250ms ease-in;
|
||||||
|
|
||||||
|
&.removed {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
background: var(--bg-light-100);
|
background: var(--bg-light-100);
|
||||||
border: 1px solid var(--bg-light-600);
|
border: 1px solid var(--bg-light-600);
|
||||||
@@ -277,6 +317,10 @@ main {
|
|||||||
|
|
||||||
& .title {
|
& .title {
|
||||||
font-weight: var(--ft-sans-wt-bold);
|
font-weight: var(--ft-sans-wt-bold);
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-weight: var(--ft-sans-wt-normal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +345,9 @@ main {
|
|||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
min-width: 4rem;
|
min-width: 4rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,6 +432,15 @@ button,
|
|||||||
background: var(--bg-light-700);
|
background: var(--bg-light-700);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.delete,
|
||||||
|
&.error {
|
||||||
|
color: #e62929;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e629291d;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='text'],
|
input[type='text'],
|
||||||
@@ -408,6 +464,11 @@ textarea {
|
|||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
font-family: var(--ft-mono);
|
font-family: var(--ft-mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:read-only {
|
||||||
|
color: var(--fg-light-300);
|
||||||
|
background: var(--bg-light-500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
@@ -540,51 +601,36 @@ $heading-scale: 1.33;
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.delete {
|
.route-buckets {
|
||||||
color: #e62929;
|
.table {
|
||||||
|
min-width: 30rem;
|
||||||
|
|
||||||
&:hover {
|
.cells {
|
||||||
background: #e629291d;
|
grid-template-columns: 1fr auto auto;
|
||||||
|
gap: 0.25rem 1rem;
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
min-width: 20rem;
|
||||||
|
|
||||||
|
input[type='text'] {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.toasts {
|
.route-bucket {
|
||||||
position: absolute;
|
.table {
|
||||||
z-index: 10;
|
min-width: 30rem;
|
||||||
|
|
||||||
left: 50%;
|
.cells {
|
||||||
transform: translateX(-50%);
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: 0.25rem 1rem;
|
||||||
bottom: 1rem;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
gap: 0.25rem;
|
|
||||||
|
|
||||||
transition: all 150ms linear;
|
|
||||||
|
|
||||||
font-size: 15px;
|
|
||||||
|
|
||||||
.toast {
|
|
||||||
background: #38383d;
|
|
||||||
color: var(--fg-dark-500);
|
|
||||||
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
|
|
||||||
opacity: 0.95;
|
|
||||||
|
|
||||||
animation: toast-fade-in 250ms ease-out;
|
|
||||||
transition: opacity 250ms ease-in;
|
|
||||||
|
|
||||||
&.removed {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-20
@@ -29,33 +29,40 @@ type Database interface {
|
|||||||
|
|
||||||
// API Keys
|
// API Keys
|
||||||
|
|
||||||
|
AllAPIKeys() ([]string, error)
|
||||||
|
|
||||||
CreateAPIKey() (string, error)
|
CreateAPIKey() (string, error)
|
||||||
CheckAPIKey(key string) error
|
CheckAPIKey(key string) error
|
||||||
RemoveAPIKey(key string) error
|
RemoveAPIKey(key string) error
|
||||||
AllAPIKeys() ([]string, error)
|
|
||||||
|
|
||||||
// Bucket Operations
|
// Bucket Operations
|
||||||
|
|
||||||
CreateBucket(bucket string, options ...any) error
|
|
||||||
DeleteBucket(bucket string) error
|
|
||||||
|
|
||||||
AllBuckets() ([]string, error)
|
AllBuckets() ([]string, error)
|
||||||
|
|
||||||
|
CreateBucket(bucket string, settings any) error
|
||||||
|
GetBucketSettings(bucket string) (any, error)
|
||||||
|
SetBucketSettings(bucket string, settings any) error
|
||||||
|
DeleteBucket(bucket string) error
|
||||||
|
|
||||||
// Bucket Object Operations
|
// Bucket Object Operations
|
||||||
|
|
||||||
CreateBucketObject(bucket string, r io.Reader) (string, error)
|
AllBucketObjects(bucket string) ([]string, error)
|
||||||
DeleteBucketObject(bucket, id string) error
|
|
||||||
|
|
||||||
|
CreateBucketObject(bucket string, r io.Reader) (string, error)
|
||||||
GetBucketObject(bucket, id string, w io.Writer) error
|
GetBucketObject(bucket, id string, w io.Writer) error
|
||||||
SetBucketObject(bucket, id string, r io.Reader) error
|
SetBucketObject(bucket, id string, r io.Reader) error
|
||||||
|
DeleteBucketObject(bucket, id string) error
|
||||||
|
}
|
||||||
|
|
||||||
AllBucketObjects(bucket string) ([]string, error)
|
type JsonBucketSettings struct {
|
||||||
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsonBucket struct {
|
type jsonBucket struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Path string `json:"path"`
|
|
||||||
Objects []string `json:"objects"`
|
Objects []string `json:"objects"`
|
||||||
|
|
||||||
|
Settings *JsonBucketSettings `json:"settings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsonDB struct {
|
type jsonDB struct {
|
||||||
@@ -207,7 +214,7 @@ func (db *jsonDB) CheckAPIKey(key string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !slices.Contains(db.APIKeys, key) {
|
if !slices.Contains(db.APIKeys, key) {
|
||||||
return fmt.Errorf("the given API key %q is invalid", key)
|
return fmt.Errorf("the given api key %q is invalid", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -220,7 +227,7 @@ func (db *jsonDB) RemoveAPIKey(key string) error {
|
|||||||
|
|
||||||
i := slices.Index(db.APIKeys, key)
|
i := slices.Index(db.APIKeys, key)
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
return fmt.Errorf("the given API key %q is invalid", key)
|
return fmt.Errorf("the given api key %q is invalid", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
db.APIKeys = append(db.APIKeys[:i], db.APIKeys[i+1:]...)
|
db.APIKeys = append(db.APIKeys[:i], db.APIKeys[i+1:]...)
|
||||||
@@ -232,7 +239,11 @@ func (db *jsonDB) RemoveAPIKey(key string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *jsonDB) CreateBucket(bucket string, options ...any) error {
|
//
|
||||||
|
// Bucket Methods
|
||||||
|
//
|
||||||
|
|
||||||
|
func (db *jsonDB) CreateBucket(bucket string, settings any) error {
|
||||||
if err := db.load(); err != nil {
|
if err := db.load(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -242,12 +253,15 @@ func (db *jsonDB) CreateBucket(bucket string, options ...any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bi := &jsonBucket{
|
bi := &jsonBucket{
|
||||||
Name: bucket,
|
Name: bucket,
|
||||||
Path: bucket + "/",
|
Settings: &JsonBucketSettings{
|
||||||
|
Path: bucket + "/",
|
||||||
|
},
|
||||||
Objects: []string{},
|
Objects: []string{},
|
||||||
}
|
}
|
||||||
if len(options) > 0 {
|
|
||||||
bi.Path = options[0].(string)
|
if settings != nil {
|
||||||
|
bi.Settings = settings.(*JsonBucketSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Buckets[bucket] = bi
|
db.Buckets[bucket] = bi
|
||||||
@@ -259,6 +273,39 @@ func (db *jsonDB) CreateBucket(bucket string, options ...any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *jsonDB) GetBucketSettings(bucket string) (any, error) {
|
||||||
|
if err := db.load(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := db.Buckets[bucket]; !found {
|
||||||
|
return nil, fmt.Errorf("bucket named %q not found", bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Buckets[bucket].Settings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *jsonDB) SetBucketSettings(bucket string, settings any) error {
|
||||||
|
if err := db.load(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, found := db.Buckets[bucket]
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("bucket named %q not found", bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Settings = settings.(*JsonBucketSettings)
|
||||||
|
|
||||||
|
log.Printf("[Warning] Bucket %q settings changed but bucket migration is not yet implemented (move the folder yourself)", bucket)
|
||||||
|
|
||||||
|
if err := db.store(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *jsonDB) DeleteBucket(bucket string) error {
|
func (db *jsonDB) DeleteBucket(bucket string) error {
|
||||||
if err := db.load(); err != nil {
|
if err := db.load(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -303,7 +350,7 @@ func (db *jsonDB) CreateBucketObject(bucket string, r io.Reader) (string, error)
|
|||||||
return "", fmt.Errorf("bucket named %q not found", bucket)
|
return "", fmt.Errorf("bucket named %q not found", bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := store.Create(b.Path, db.PrefixSize, r)
|
id, err := store.Create(b.Settings.Path, db.PrefixSize, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -331,7 +378,7 @@ func (db *jsonDB) SetBucketObject(bucket, id string, r io.Reader) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return store.Update(b.Path, db.PrefixSize, id, r)
|
return store.Update(b.Settings.Path, db.PrefixSize, id, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *jsonDB) GetBucketObject(bucket, id string, w io.Writer) error {
|
func (db *jsonDB) GetBucketObject(bucket, id string, w io.Writer) error {
|
||||||
@@ -344,7 +391,7 @@ func (db *jsonDB) GetBucketObject(bucket, id string, w io.Writer) error {
|
|||||||
return fmt.Errorf("bucket named %q not found", bucket)
|
return fmt.Errorf("bucket named %q not found", bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
return store.Read(b.Path, db.PrefixSize, id, w)
|
return store.Read(b.Settings.Path, db.PrefixSize, id, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *jsonDB) DeleteBucketObject(bucket, id string) error {
|
func (db *jsonDB) DeleteBucketObject(bucket, id string) error {
|
||||||
@@ -357,7 +404,7 @@ func (db *jsonDB) DeleteBucketObject(bucket, id string) error {
|
|||||||
return fmt.Errorf("bucket named %q not found", bucket)
|
return fmt.Errorf("bucket named %q not found", bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := store.Delete(b.Path, db.PrefixSize, id); err != nil {
|
if err := store.Delete(b.Settings.Path, db.PrefixSize, id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,7 +428,7 @@ func (db *jsonDB) AllBucketObjects(bucket string) ([]string, error) {
|
|||||||
return nil, fmt.Errorf("bucket named %q not found", bucket)
|
return nil, fmt.Errorf("bucket named %q not found", bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
objects, err := store.All(b.Path)
|
objects, err := store.All(b.Settings.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-4
@@ -22,9 +22,15 @@ func (r *Router) Api(api fiber.Router) {
|
|||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
isAPIKeyMiddleware := func(c *fiber.Ctx) error {
|
isAPIKeyMiddleware := func(c *fiber.Ctx) error {
|
||||||
if _, isAdmin := adminSessions[c.Cookies("sid")]; !isAdmin { // if admin continue
|
if _, isAdmin := adminSessions[c.Cookies("sid")]; !isAdmin { // if admin continue
|
||||||
if err := r.Database.CheckAPIKey(c.Cookies("token")); err != nil { // otherwise also check api token
|
token := c.Cookies("token")
|
||||||
|
if token == "" {
|
||||||
|
return fmt.Errorf("no api token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Database.CheckAPIKey(token); err != nil { // otherwise also check api token
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,12 +126,12 @@ func (r *Router) Api(api fiber.Router) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []any{}
|
settings := &database.JsonBucketSettings{}
|
||||||
if req.Path != "" {
|
if req.Path != "" {
|
||||||
opts = append(opts, req.Path)
|
settings.Path = req.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.Database.CreateBucket(req.Bucket, opts...); err != nil {
|
if err := r.Database.CreateBucket(req.Bucket, settings); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +151,37 @@ func (r *Router) Api(api fiber.Router) {
|
|||||||
return c.JSON(objects)
|
return c.JSON(objects)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
api.Get("/buckets/:bucket/settings",
|
||||||
|
isAdminMiddleware,
|
||||||
|
func(c *fiber.Ctx) error {
|
||||||
|
bucket := c.Params("bucket")
|
||||||
|
|
||||||
|
settings, err := r.Database.GetBucketSettings(bucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(settings)
|
||||||
|
})
|
||||||
|
|
||||||
|
api.Post("/buckets/:bucket/settings",
|
||||||
|
isAdminMiddleware,
|
||||||
|
func(c *fiber.Ctx) error {
|
||||||
|
bucket := c.Params("bucket")
|
||||||
|
|
||||||
|
var settings *database.JsonBucketSettings
|
||||||
|
|
||||||
|
if err := c.BodyParser(&settings); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Database.SetBucketSettings(bucket, settings); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON("ok")
|
||||||
|
})
|
||||||
|
|
||||||
api.Post("/buckets/:bucket",
|
api.Post("/buckets/:bucket",
|
||||||
isAPIKeyMiddleware,
|
isAPIKeyMiddleware,
|
||||||
func(c *fiber.Ctx) error {
|
func(c *fiber.Ctx) error {
|
||||||
@@ -186,6 +223,19 @@ func (r *Router) Api(api fiber.Router) {
|
|||||||
return c.SendStream(buf)
|
return c.SendStream(buf)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
api.Delete("/buckets/:bucket/:id",
|
||||||
|
isAPIKeyMiddleware,
|
||||||
|
func(c *fiber.Ctx) error {
|
||||||
|
bucket := c.Params("bucket")
|
||||||
|
id := c.Params("id")
|
||||||
|
|
||||||
|
if err := r.Database.DeleteBucketObject(bucket, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON("ok")
|
||||||
|
})
|
||||||
|
|
||||||
//
|
//
|
||||||
// API Keys
|
// API Keys
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user