Добавил /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
|
||||
node: iinet.ru
|
||||
logger_type: 3
|
||||
|
||||
echos:
|
||||
node.local:
|
||||
description: Локалка
|
||||
|
@ -47,3 +48,5 @@ fetch:
|
|||
- music.14
|
||||
- std.english
|
||||
- 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.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/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(`GET /m/{msgID}`, a.getMessageHandler)
|
||||
mux.HandleFunc(`GET /e/{id}`, a.getEchoHandler)
|
||||
mux.HandleFunc(`GET /x/features`, a.getFeaturesHandler)
|
||||
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{
|
||||
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 (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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) {
|
||||
// TODO
|
||||
func (a *API) getBlacklistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
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) {
|
||||
msg, pauth := r.PathValue("tmsg"), r.PathValue("pauth")
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
form := r.PostForm
|
||||
a.savePointMessage(w, form.Get("tmsg"), form.Get("pauth"))
|
||||
}
|
||||
|
||||
func (a *API) getPointHandler(w http.ResponseWriter, r *http.Request) {
|
||||
a.savePointMessage(w, r.PathValue("tmsg"), r.PathValue("pauth"))
|
||||
form := r.PostForm
|
||||
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 {
|
||||
|
|
|
@ -7,12 +7,13 @@ import (
|
|||
)
|
||||
|
||||
type Config struct {
|
||||
Listen string `yaml:"listen"`
|
||||
Node string `yaml:"node"`
|
||||
Store string `yaml:"store"`
|
||||
LoggerType int `yaml:"logger_type"`
|
||||
Echos map[string]Echo `yaml:"echos"`
|
||||
Fetch []Node `yaml:"fetch"`
|
||||
Listen string `yaml:"listen"`
|
||||
Node string `yaml:"node"`
|
||||
Store string `yaml:"store"`
|
||||
LoggerType int `yaml:"logger_type"`
|
||||
Echos map[string]Echo `yaml:"echos"`
|
||||
Fetch []Node `yaml:"fetch"`
|
||||
FilesDirectory string `yaml:"files_directory"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
|
|
|
@ -43,6 +43,9 @@ func (f *Fetcher) Run(ctx context.Context) error {
|
|||
if err := f.downloadMessages(node, messagesToDownloads); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.downloadBlacklist(node); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Println("finished")
|
||||
return nil
|
||||
|
@ -133,6 +136,22 @@ func (f *Fetcher) downloadMessagesChunk(node config.Node, messages []string) err
|
|||
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 {
|
||||
segments := []string{node.Addr, method}
|
||||
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 (
|
||||
msgBucket = "_msg"
|
||||
points = "_points"
|
||||
blacklist = "_blacklist"
|
||||
)
|
||||
|
||||
type IDEC struct {
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
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)
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if point.AuthString == auth {
|
||||
if point.AuthString == pauth {
|
||||
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