Добавил /x/file*

Добавил Dockerfile
This commit is contained in:
Александр Кирюхин 2024-10-20 20:38:08 +03:00
parent d9e19fc53f
commit b26bd10926
Signed by: neonxp
SSH key fingerprint: SHA256:SVt7TjxbVc87m1QYaQziOJ0N3OCFURv2g76gD/UTTXI
16 changed files with 280 additions and 17 deletions

23
Dockerfile Normal file
View 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"]

View file

@ -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
View file

@ -0,0 +1 @@
Private node files

1
files/pub/.gitkeep Normal file
View file

@ -0,0 +1 @@
Public node files

1
files/usr/.gitkeep Normal file
View file

@ -0,0 +1 @@
Users node files

View file

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

View file

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

View file

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

View file

@ -7,12 +7,13 @@ import (
) )
type Config struct { type Config struct {
Listen string `yaml:"listen"` Listen string `yaml:"listen"`
Node string `yaml:"node"` Node string `yaml:"node"`
Store string `yaml:"store"` Store string `yaml:"store"`
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 {

View file

@ -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
View 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
View 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
}

View file

@ -20,6 +20,7 @@ var (
const ( const (
msgBucket = "_msg" msgBucket = "_msg"
points = "_points" points = "_points"
blacklist = "_blacklist"
) )
type IDEC struct { type IDEC struct {

View file

@ -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
View file

@ -0,0 +1,7 @@
package model
type File struct {
Name string
Size int64
FullName string
}