파일 업로드/삭제, maintenance 메뉴 추가

This commit is contained in:
2023-06-05 11:56:34 +09:00
parent 827abf34fe
commit 41387ba902
3 changed files with 199 additions and 73 deletions

View File

@ -2,6 +2,7 @@ package core
import ( import (
"bytes" "bytes"
"crypto/md5"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@ -174,78 +175,133 @@ func (caller apiCaller) isValidUser(service any, category string) (valid bool, a
} }
func (caller apiCaller) filesAPI(w http.ResponseWriter, r *http.Request) error { func (caller apiCaller) filesAPI(w http.ResponseWriter, r *http.Request) error {
serviceid := r.FormValue("service") if r.Method == "GET" {
if len(serviceid) == 0 { hasAuth := caller.isGlobalAdmin()
serviceid = "000000000000" var email string
if !*noauth {
v, ok := caller.userinfo["email"]
if !ok {
return nil
}
email = v.(string)
_, hasAuth = caller.admins[email]
}
servicename := r.FormValue("service")
sh := caller.mg.services.get(servicename)
if sh == nil {
w.WriteHeader(http.StatusBadRequest)
return nil
}
if !hasAuth {
if hasAuth = sh.isValidAPIUser("maintenance", email); !hasAuth {
hasAuth = sh.isValidAPIUser("service", email)
}
}
if !hasAuth {
w.WriteHeader(http.StatusBadRequest)
return nil
}
var files []fileDocumentDesc
err := caller.mg.mongoClient.FindAllAs(CollectionFile, bson.M{
"service": servicename,
}, &files, options.Find().SetProjection(bson.M{
"contents": 0,
}))
if err != nil {
return err
}
if len(files) > 0 {
enc := json.NewEncoder(w)
return enc.Encode(files)
}
} else if r.Method == "DELETE" {
servicename := r.FormValue("service")
key := r.FormValue("key")
if len(servicename) == 0 || len(key) == 0 {
w.WriteHeader(http.StatusBadRequest)
return nil
}
_, err := caller.mg.mongoClient.Delete(CollectionFile, bson.M{
"service": servicename,
"key": key,
})
if err != nil {
return err
}
} }
var files []fileDocumentDesc return nil
err := caller.mg.mongoClient.FindAllAs(CollectionFile, bson.M{
"service": serviceid,
}, &files, options.Find().SetProjection(bson.M{
"contents": 0,
}))
if err != nil {
return err
}
enc := json.NewEncoder(w)
return enc.Encode(files)
} }
var seq = uint32(0) var seq = uint32(0)
func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error { func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error {
serviceid := r.FormValue("service") if r.Method == "PUT" {
if len(serviceid) == 0 { servicename := r.FormValue("service")
serviceid = "000000000000" hasher := md5.New()
} hasher.Write([]byte(servicename))
subfolder := hex.EncodeToString(hasher.Sum(nil))[:8]
infile, header, err := r.FormFile("file") infile, header, err := r.FormFile("file")
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return err
}
defer infile.Close()
desc := r.FormValue("desc")
contents, _ := io.ReadAll(infile)
extractstr := r.FormValue("extract")
extract, _ := strconv.ParseBool(extractstr)
var b [5]byte
binary.BigEndian.PutUint32(b[0:4], uint32(time.Now().Unix()))
b[4] = byte(atomic.AddUint32(&seq, 1) % 255)
rf := hex.EncodeToString(b[1:])
newidstr := subfolder + rf
newidbt, _ := hex.DecodeString(newidstr)
newidobj := primitive.NewObjectID()
copy(newidobj[:], newidbt[:8])
var link string
if extract {
link = path.Join("static", subfolder, rf)
} else {
link = path.Join("static", subfolder, rf, header.Filename)
}
newdoc := fileDocumentDesc{
Contents: contents,
Src: header.Filename,
Timestamp: time.Now().UTC().Unix(),
Extract: extract,
Link: link,
Desc: desc,
Key: rf,
Service: servicename,
}
_, _, err = caller.mg.mongoClient.UpsertOne(CollectionFile, bson.M{
"_id": newidobj,
"service": servicename,
"key": rf,
}, newdoc)
if err == nil {
newdoc.Contents = nil
enc := json.NewEncoder(w)
enc.Encode(newdoc)
}
return err return err
} }
defer infile.Close() return nil
desc := r.FormValue("desc")
contents, _ := io.ReadAll(infile)
extractstr := r.FormValue("extract")
extract, _ := strconv.ParseBool(extractstr)
var b [5]byte
binary.BigEndian.PutUint32(b[0:4], uint32(time.Now().Unix()))
b[4] = byte(atomic.AddUint32(&seq, 1) % 255)
rf := hex.EncodeToString(b[:])
var link string
if extract {
link = path.Join("static", serviceid, rf)
} else {
link = path.Join("static", serviceid, rf, header.Filename)
}
newdoc := fileDocumentDesc{
Contents: contents,
Src: header.Filename,
Timestamp: time.Now().UTC().Unix(),
Extract: extract,
Link: link,
Desc: desc,
Key: rf,
Service: serviceid,
}
_, _, err = caller.mg.mongoClient.UpsertOne(CollectionFile, bson.M{
"service": serviceid,
"key": rf,
}, newdoc)
if err == nil {
newdoc.Contents = nil
enc := json.NewEncoder(w)
enc.Encode(newdoc)
}
return err
} }
func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) error { func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) error {
@ -387,6 +443,51 @@ func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error
return nil return nil
} }
func (caller apiCaller) maintenanceAPI(w http.ResponseWriter, r *http.Request) error {
mg := caller.mg
queryvals := r.URL.Query()
if r.Method == "GET" {
name := queryvals.Get("name")
if len(name) > 0 {
if valid, _ := caller.isValidUser(name, "*"); !valid {
w.WriteHeader(http.StatusBadRequest)
return nil
}
if valid, admin := caller.isValidUser(name, "maintenance"); valid || admin {
w.Header().Add("MG-X-SERVICE-EDITABLE", name)
}
serptr := atomic.LoadPointer(&mg.services.get(name).divisionsSerialized)
w.Write(*(*[]byte)(serptr))
} else {
caller.writeAccessableServices(w)
}
} else if r.Method == "POST" {
servicename := r.FormValue("name")
var divs map[string]*division
dec := json.NewDecoder(r.Body)
if err := dec.Decode(&divs); err != nil {
w.WriteHeader(http.StatusBadRequest)
return err
}
_, _, err := mg.mongoClient.Update(CollectionService, bson.M{
"service": servicename,
}, bson.M{
"$set": bson.M{"divisions": divs},
}, options.Update().SetUpsert(false))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return err
}
}
return nil
}
func (caller apiCaller) accountAPI(w http.ResponseWriter, r *http.Request) error { func (caller apiCaller) accountAPI(w http.ResponseWriter, r *http.Request) error {
mg := caller.mg mg := caller.mg
queryvals := r.URL.Query() queryvals := r.URL.Query()
@ -604,6 +705,8 @@ func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) {
err = caller.accountAPI(w, r) err = caller.accountAPI(w, r)
} else if strings.HasSuffix(r.URL.Path, "/upload") { } else if strings.HasSuffix(r.URL.Path, "/upload") {
err = caller.uploadAPI(w, r) err = caller.uploadAPI(w, r)
} else if strings.HasSuffix(r.URL.Path, "/maintenance") {
err = caller.maintenanceAPI(w, r)
} else if strings.HasSuffix(r.URL.Path, "/files") { } else if strings.HasSuffix(r.URL.Path, "/files") {
err = caller.filesAPI(w, r) err = caller.filesAPI(w, r)
} }

View File

@ -172,16 +172,19 @@ type division struct {
Maintenance *maintenance `bson:",omitempty" json:",omitempty"` Maintenance *maintenance `bson:",omitempty" json:",omitempty"`
} }
type ServiceDescriptionSummary struct {
Id primitive.ObjectID `bson:"_id"`
ServiceName string `bson:"service"`
ServiceCode string `bson:"code"`
UseWhitelist bool `bson:"use_whitelist"`
Closed bool `bson:"closed"`
}
type serviceDescription struct { type serviceDescription struct {
// sync.Mutex ServiceDescriptionSummary `bson:",inline"`
Id primitive.ObjectID `bson:"_id"` Divisions map[string]*division `bson:"divisions"`
ServiceName string `bson:"service"` ServerApiTokens []primitive.ObjectID `bson:"api_tokens"`
Divisions map[string]*division `bson:"divisions"` ApiUsers map[string][]string `bson:"api_users"`
ServiceCode string `bson:"code"`
UseWhitelist bool `bson:"use_whitelist"`
Closed bool `bson:"closed"`
ServerApiTokens []primitive.ObjectID `bson:"api_tokens"`
ApiUsers map[string][]string `bson:"api_users"`
auths *common.AuthCollection auths *common.AuthCollection
wl whitelist wl whitelist
@ -327,7 +330,7 @@ func (sh *serviceDescription) prepare(mg *Maingate) error {
mg.apiTokenToService.add(keyid.Hex(), sh.ServiceCode) mg.apiTokenToService.add(keyid.Hex(), sh.ServiceCode)
} }
bt, _ := json.Marshal(sh) bt, _ := json.Marshal(sh.ServiceDescriptionSummary)
atomic.StorePointer(&sh.serviceSerialized, unsafe.Pointer(&bt)) atomic.StorePointer(&sh.serviceSerialized, unsafe.Pointer(&bt))
logger.Println("service is ready :", sh.ServiceName, sh.ServiceCode, sh.UseWhitelist, sh.ApiUsers, string(divmarshaled)) logger.Println("service is ready :", sh.ServiceName, sh.ServiceCode, sh.UseWhitelist, sh.ApiUsers, string(divmarshaled))

View File

@ -2,7 +2,10 @@ package core
import ( import (
"context" "context"
"encoding/hex"
"net/http" "net/http"
"os"
"path"
"sync/atomic" "sync/atomic"
"time" "time"
@ -32,6 +35,10 @@ type servicePipelineDocument struct {
} }
type filePipelineDocument struct { type filePipelineDocument struct {
OperationType string `bson:"operationType"`
DocumentKey struct {
Id primitive.ObjectID `bson:"_id"`
} `bson:"documentKey"`
File *fileDocumentDesc `bson:"fullDocument"` File *fileDocumentDesc `bson:"fullDocument"`
} }
@ -140,6 +147,7 @@ func (mg *Maingate) watchFileCollection(parentctx context.Context, serveMux *htt
Key: "$match", Value: bson.D{ Key: "$match", Value: bson.D{
{Key: "operationType", Value: bson.D{ {Key: "operationType", Value: bson.D{
{Key: "$in", Value: bson.A{ {Key: "$in", Value: bson.A{
"delete",
"insert", "insert",
}}, }},
}}, }},
@ -148,6 +156,8 @@ func (mg *Maingate) watchFileCollection(parentctx context.Context, serveMux *htt
projectStage := bson.D{ projectStage := bson.D{
{ {
Key: "$project", Value: bson.D{ Key: "$project", Value: bson.D{
{Key: "operationType", Value: 1},
{Key: "documentKey", Value: 1},
{Key: "fullDocument", Value: 1}, {Key: "fullDocument", Value: 1},
}, },
}, },
@ -186,7 +196,17 @@ func (mg *Maingate) watchFileCollection(parentctx context.Context, serveMux *htt
var data filePipelineDocument var data filePipelineDocument
if err := stream.Decode(&data); err == nil { if err := stream.Decode(&data); err == nil {
data.File.save() switch data.OperationType {
case "insert":
data.File.save()
case "delete":
subfolder := hex.EncodeToString(data.DocumentKey.Id[:4])
rf := hex.EncodeToString(data.DocumentKey.Id[4:8])
subpath := path.Join("static", subfolder, rf)
os.RemoveAll(subpath)
}
} }
} }
} }