파일 업로드/삭제, maintenance 메뉴 추가
This commit is contained in:
227
core/api.go
227
core/api.go
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user