Добавил /x/file*
Добавил Dockerfile
This commit is contained in:
parent
d9e19fc53f
commit
b26bd10926
16 changed files with 280 additions and 17 deletions
23
Dockerfile
Normal file
23
Dockerfile
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Build backend
|
||||||
|
FROM golang:1.23.2-alpine3.20 AS backend
|
||||||
|
ARG VERSION
|
||||||
|
WORKDIR /srv
|
||||||
|
RUN apk update --no-cache && apk add --no-cache tzdata
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download && go mod verify
|
||||||
|
COPY . .
|
||||||
|
RUN go build -o app .
|
||||||
|
|
||||||
|
# Runtime container
|
||||||
|
FROM alpine:3.20
|
||||||
|
WORKDIR /srv
|
||||||
|
RUN apk update --no-cache && apk add --no-cache ca-certificates
|
||||||
|
|
||||||
|
COPY --from=backend /usr/share/zoneinfo/Europe/Moscow /usr/share/zoneinfo/Europe/Moscow
|
||||||
|
COPY --from=backend /srv/app /srv/app
|
||||||
|
|
||||||
|
ENV TZ=Europe/Moscow
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/srv/app"]
|
|
@ -2,6 +2,7 @@ listen: :8000
|
||||||
store: ./store.db
|
store: ./store.db
|
||||||
node: iinet.ru
|
node: iinet.ru
|
||||||
logger_type: 3
|
logger_type: 3
|
||||||
|
|
||||||
echos:
|
echos:
|
||||||
node.local:
|
node.local:
|
||||||
description: Локалка
|
description: Локалка
|
||||||
|
@ -47,3 +48,5 @@ fetch:
|
||||||
- music.14
|
- music.14
|
||||||
- std.english
|
- std.english
|
||||||
- difrex.blog
|
- difrex.blog
|
||||||
|
|
||||||
|
files_directory: files
|
1
files/priv/.gitkeep
Normal file
1
files/priv/.gitkeep
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Private node files
|
1
files/pub/.gitkeep
Normal file
1
files/pub/.gitkeep
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Public node files
|
1
files/usr/.gitkeep
Normal file
1
files/usr/.gitkeep
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Users node files
|
|
@ -30,15 +30,19 @@ func (a *API) Run(ctx context.Context) error {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.HandleFunc(`GET /list.txt`, a.getListHandler)
|
mux.HandleFunc(`GET /list.txt`, a.getListHandler)
|
||||||
mux.HandleFunc(`GET /blacklist.txt`, a.getBlacklistTxtHandler)
|
mux.HandleFunc(`GET /blacklist.txt`, a.getBlacklistHandler)
|
||||||
mux.HandleFunc(`GET /u/e/{ids...}`, a.getEchosHandler)
|
mux.HandleFunc(`GET /u/e/{ids...}`, a.getEchosHandler)
|
||||||
mux.HandleFunc(`GET /u/m/{ids...}`, a.getBundleHandler)
|
mux.HandleFunc(`GET /u/m/{ids...}`, a.getBundleHandler)
|
||||||
mux.HandleFunc(`GET /u/point/{pauth}/{tmsg}`, a.getPointHandler)
|
mux.HandleFunc(`GET /u/point/{pauth}/{tmsg}`, a.postPointHandler)
|
||||||
mux.HandleFunc(`POST /u/point`, a.postPointHandler)
|
mux.HandleFunc(`POST /u/point`, a.postPointHandler)
|
||||||
mux.HandleFunc(`GET /m/{msgID}`, a.getMessageHandler)
|
mux.HandleFunc(`GET /m/{msgID}`, a.getMessageHandler)
|
||||||
mux.HandleFunc(`GET /e/{id}`, a.getEchoHandler)
|
mux.HandleFunc(`GET /e/{id}`, a.getEchoHandler)
|
||||||
mux.HandleFunc(`GET /x/features`, a.getFeaturesHandler)
|
mux.HandleFunc(`GET /x/features`, a.getFeaturesHandler)
|
||||||
mux.HandleFunc(`GET /x/c/{ids...}`, a.getEchosInfo)
|
mux.HandleFunc(`GET /x/c/{ids...}`, a.getEchosInfo)
|
||||||
|
mux.HandleFunc(`POST /x/filelist`, a.getFilelistHandler)
|
||||||
|
mux.HandleFunc(`GET /x/filelist/{pauth}`, a.getFilelistHandler)
|
||||||
|
mux.HandleFunc(`POST /x/file`, a.getFileHandler)
|
||||||
|
mux.HandleFunc(`GET /x/file/{filename}`, a.getFileHandler)
|
||||||
|
|
||||||
srv := http.Server{
|
srv := http.Server{
|
||||||
Addr: a.config.Listen,
|
Addr: a.config.Listen,
|
||||||
|
|
51
pkg/api/file.go
Normal file
51
pkg/api/file.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *API) getFilelistHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pauth := r.PathValue("pauth")
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form := r.PostForm
|
||||||
|
if form.Has("pauth") {
|
||||||
|
pauth = form.Get("pauth")
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := a.idec.FilesList(pauth)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
fmt.Fprintf(w, "%s:%d:%s\n", file.FullName, file.Size, file.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) getFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filename := r.PathValue("filename")
|
||||||
|
pauth := ""
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form := r.PostForm
|
||||||
|
if form.Has("pauth") {
|
||||||
|
pauth = form.Get("pauth")
|
||||||
|
}
|
||||||
|
if form.Has("filename") {
|
||||||
|
filename = form.Get("filename")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.idec.GetFile(pauth, filename, w); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *API) getListHandler(w http.ResponseWriter, r *http.Request) {
|
func (a *API) getListHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -17,6 +18,12 @@ func (a *API) getListHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) getBlacklistTxtHandler(w http.ResponseWriter, r *http.Request) {
|
func (a *API) getBlacklistHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO
|
list, err := a.idec.GetBlacklist()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(w, strings.Join(list, "\n"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,16 +35,21 @@ func (a *API) getMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) postPointHandler(w http.ResponseWriter, r *http.Request) {
|
func (a *API) postPointHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
msg, pauth := r.PathValue("tmsg"), r.PathValue("pauth")
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
form := r.PostForm
|
|
||||||
a.savePointMessage(w, form.Get("tmsg"), form.Get("pauth"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) getPointHandler(w http.ResponseWriter, r *http.Request) {
|
form := r.PostForm
|
||||||
a.savePointMessage(w, r.PathValue("tmsg"), r.PathValue("pauth"))
|
if form.Has("tmsg") {
|
||||||
|
msg = form.Get("tmsg")
|
||||||
|
}
|
||||||
|
if form.Has("pauth") {
|
||||||
|
pauth = form.Get("pauth")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.savePointMessage(w, msg, pauth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) savePointMessage(w http.ResponseWriter, rawMessage, auth string) error {
|
func (a *API) savePointMessage(w http.ResponseWriter, rawMessage, auth string) error {
|
||||||
|
|
|
@ -13,6 +13,7 @@ type Config struct {
|
||||||
LoggerType int `yaml:"logger_type"`
|
LoggerType int `yaml:"logger_type"`
|
||||||
Echos map[string]Echo `yaml:"echos"`
|
Echos map[string]Echo `yaml:"echos"`
|
||||||
Fetch []Node `yaml:"fetch"`
|
Fetch []Node `yaml:"fetch"`
|
||||||
|
FilesDirectory string `yaml:"files_directory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
|
|
|
@ -43,6 +43,9 @@ func (f *Fetcher) Run(ctx context.Context) error {
|
||||||
if err := f.downloadMessages(node, messagesToDownloads); err != nil {
|
if err := f.downloadMessages(node, messagesToDownloads); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := f.downloadBlacklist(node); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.Println("finished")
|
log.Println("finished")
|
||||||
return nil
|
return nil
|
||||||
|
@ -133,6 +136,22 @@ func (f *Fetcher) downloadMessagesChunk(node config.Node, messages []string) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Fetcher) downloadBlacklist(node config.Node) error {
|
||||||
|
p := formatCommand(node, "blacklist.txt")
|
||||||
|
resp, err := f.client.Get(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
|
||||||
|
return f.idec.MergeBlacklist(lines)
|
||||||
|
}
|
||||||
|
|
||||||
func formatCommand(node config.Node, method string, args ...string) string {
|
func formatCommand(node config.Node, method string, args ...string) string {
|
||||||
segments := []string{node.Addr, method}
|
segments := []string{node.Addr, method}
|
||||||
segments = append(segments, args...)
|
segments = append(segments, args...)
|
||||||
|
|
65
pkg/idec/blacklist.go
Normal file
65
pkg/idec/blacklist.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package idec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gitrepo.ru/neonxp/idecnode/pkg/model"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const blacklistMessage = "<Удалено по черному списку>"
|
||||||
|
|
||||||
|
func (i *IDEC) GetBlacklist() ([]string, error) {
|
||||||
|
list := []string{}
|
||||||
|
|
||||||
|
return list, i.db.View(func(tx *bbolt.Tx) error {
|
||||||
|
bucket := tx.Bucket([]byte(blacklist))
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucket.ForEach(func(k, _ []byte) error {
|
||||||
|
list = append(list, string(k))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IDEC) MergeBlacklist(list []string) error {
|
||||||
|
return i.db.Update(func(tx *bbolt.Tx) error {
|
||||||
|
bucket, err := tx.CreateBucketIfNotExists([]byte(blacklist))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
messages, err := tx.CreateBucketIfNotExists([]byte(msgBucket))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range list {
|
||||||
|
if k == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := bucket.Put([]byte(k), []byte{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msgBytes := messages.Get([]byte(k))
|
||||||
|
if msgBytes == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg, err := model.MessageFromBundle(k, string(msgBytes))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg.Subject = blacklistMessage
|
||||||
|
msg.Message = blacklistMessage
|
||||||
|
if err := messages.Put([]byte(k), []byte(msg.Bundle())); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
74
pkg/idec/files.go
Normal file
74
pkg/idec/files.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package idec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitrepo.ru/neonxp/idecnode/pkg/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrFileNotAllowed = errors.New("file not allowed")
|
||||||
|
|
||||||
|
func (i *IDEC) FilesList(pauth string) ([]model.File, error) {
|
||||||
|
fileDirs := i.allowedDirs(pauth)
|
||||||
|
res := []model.File{}
|
||||||
|
for _, dir := range fileDirs {
|
||||||
|
files, err := filepath.Glob(dir + "/*")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
fi, err := os.Stat(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, model.File{
|
||||||
|
Name: fi.Name(),
|
||||||
|
Size: fi.Size(),
|
||||||
|
FullName: strings.TrimPrefix(f, i.config.FilesDirectory),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IDEC) GetFile(pauth string, path string, w io.Writer) error {
|
||||||
|
file := filepath.Clean(filepath.Join(i.config.FilesDirectory, path))
|
||||||
|
allowed := false
|
||||||
|
allowedDirs := i.allowedDirs(pauth)
|
||||||
|
for _, dir := range allowedDirs {
|
||||||
|
if filepath.HasPrefix(file, dir) {
|
||||||
|
allowed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
return ErrFileNotAllowed
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(w, fp)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IDEC) allowedDirs(pauth string) []string {
|
||||||
|
fileDirs := []string{
|
||||||
|
filepath.Join(i.config.FilesDirectory, "pub"),
|
||||||
|
}
|
||||||
|
if pauth != "" {
|
||||||
|
if _, err := i.GetPointByAuth(pauth); err == nil {
|
||||||
|
fileDirs = append(fileDirs, filepath.Join(i.config.FilesDirectory, "priv"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileDirs
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ var (
|
||||||
const (
|
const (
|
||||||
msgBucket = "_msg"
|
msgBucket = "_msg"
|
||||||
points = "_points"
|
points = "_points"
|
||||||
|
blacklist = "_blacklist"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IDEC struct {
|
type IDEC struct {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
var errPointFound = errors.New("point found")
|
var errPointFound = errors.New("point found")
|
||||||
|
|
||||||
func (i *IDEC) GetPointByAuth(auth string) (*model.Point, error) {
|
func (i *IDEC) GetPointByAuth(pauth string) (*model.Point, error) {
|
||||||
point := new(model.Point)
|
point := new(model.Point)
|
||||||
|
|
||||||
return point, i.db.View(func(tx *bbolt.Tx) error {
|
return point, i.db.View(func(tx *bbolt.Tx) error {
|
||||||
|
@ -25,7 +25,7 @@ func (i *IDEC) GetPointByAuth(auth string) (*model.Point, error) {
|
||||||
if err := gob.NewDecoder(bytes.NewBuffer(v)).Decode(point); err != nil {
|
if err := gob.NewDecoder(bytes.NewBuffer(v)).Decode(point); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if point.AuthString == auth {
|
if point.AuthString == pauth {
|
||||||
return errPointFound
|
return errPointFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
7
pkg/model/file.go
Normal file
7
pkg/model/file.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
FullName string
|
||||||
|
}
|
Loading…
Reference in a new issue