From 41387ba902dd181aa62de44d80020397f0671efa Mon Sep 17 00:00:00 2001 From: mountain Date: Mon, 5 Jun 2023 11:56:34 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C/=EC=82=AD=EC=A0=9C,=20maintenance=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/api.go | 227 +++++++++++++++++++++++++++++++++++------------- core/service.go | 23 ++--- core/watch.go | 22 ++++- 3 files changed, 199 insertions(+), 73 deletions(-) diff --git a/core/api.go b/core/api.go index 532613f..cc37c0b 100644 --- a/core/api.go +++ b/core/api.go @@ -2,6 +2,7 @@ package core import ( "bytes" + "crypto/md5" "encoding/binary" "encoding/hex" "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 { - serviceid := r.FormValue("service") - if len(serviceid) == 0 { - serviceid = "000000000000" + if r.Method == "GET" { + hasAuth := caller.isGlobalAdmin() + 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 - 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) + return nil } var seq = uint32(0) func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error { - serviceid := r.FormValue("service") - if len(serviceid) == 0 { - serviceid = "000000000000" - } + if r.Method == "PUT" { + servicename := r.FormValue("service") + hasher := md5.New() + hasher.Write([]byte(servicename)) + subfolder := hex.EncodeToString(hasher.Sum(nil))[:8] - infile, header, err := r.FormFile("file") - if err != nil { - w.WriteHeader(http.StatusBadRequest) + infile, header, err := r.FormFile("file") + if err != nil { + 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 } - 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[:]) - - 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 + return nil } 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 } +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 { mg := caller.mg queryvals := r.URL.Query() @@ -604,6 +705,8 @@ func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) { err = caller.accountAPI(w, r) } else if strings.HasSuffix(r.URL.Path, "/upload") { 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") { err = caller.filesAPI(w, r) } diff --git a/core/service.go b/core/service.go index 2b43d0f..1046d65 100644 --- a/core/service.go +++ b/core/service.go @@ -172,16 +172,19 @@ type division struct { 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 { - // sync.Mutex - Id primitive.ObjectID `bson:"_id"` - ServiceName string `bson:"service"` - Divisions map[string]*division `bson:"divisions"` - 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"` + ServiceDescriptionSummary `bson:",inline"` + Divisions map[string]*division `bson:"divisions"` + ServerApiTokens []primitive.ObjectID `bson:"api_tokens"` + ApiUsers map[string][]string `bson:"api_users"` auths *common.AuthCollection wl whitelist @@ -327,7 +330,7 @@ func (sh *serviceDescription) prepare(mg *Maingate) error { mg.apiTokenToService.add(keyid.Hex(), sh.ServiceCode) } - bt, _ := json.Marshal(sh) + bt, _ := json.Marshal(sh.ServiceDescriptionSummary) atomic.StorePointer(&sh.serviceSerialized, unsafe.Pointer(&bt)) logger.Println("service is ready :", sh.ServiceName, sh.ServiceCode, sh.UseWhitelist, sh.ApiUsers, string(divmarshaled)) diff --git a/core/watch.go b/core/watch.go index 738706d..705e02c 100644 --- a/core/watch.go +++ b/core/watch.go @@ -2,7 +2,10 @@ package core import ( "context" + "encoding/hex" "net/http" + "os" + "path" "sync/atomic" "time" @@ -32,6 +35,10 @@ type servicePipelineDocument struct { } type filePipelineDocument struct { + OperationType string `bson:"operationType"` + DocumentKey struct { + Id primitive.ObjectID `bson:"_id"` + } `bson:"documentKey"` File *fileDocumentDesc `bson:"fullDocument"` } @@ -140,6 +147,7 @@ func (mg *Maingate) watchFileCollection(parentctx context.Context, serveMux *htt Key: "$match", Value: bson.D{ {Key: "operationType", Value: bson.D{ {Key: "$in", Value: bson.A{ + "delete", "insert", }}, }}, @@ -148,6 +156,8 @@ func (mg *Maingate) watchFileCollection(parentctx context.Context, serveMux *htt projectStage := bson.D{ { Key: "$project", Value: bson.D{ + {Key: "operationType", Value: 1}, + {Key: "documentKey", Value: 1}, {Key: "fullDocument", Value: 1}, }, }, @@ -186,7 +196,17 @@ func (mg *Maingate) watchFileCollection(parentctx context.Context, serveMux *htt var data filePipelineDocument 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) + } } } }