github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/project/project.go (about) 1 // project management 2 3 package project 4 5 import ( 6 "context" 7 8 "github.com/pkg/errors" 9 10 confid "github.com/machinefi/w3bstream/pkg/depends/conf/id" 11 "github.com/machinefi/w3bstream/pkg/depends/conf/jwt" 12 "github.com/machinefi/w3bstream/pkg/depends/conf/logger" 13 "github.com/machinefi/w3bstream/pkg/depends/kit/logr" 14 "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx" 15 "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx/datatypes" 16 "github.com/machinefi/w3bstream/pkg/errors/status" 17 "github.com/machinefi/w3bstream/pkg/models" 18 "github.com/machinefi/w3bstream/pkg/modules/account" 19 "github.com/machinefi/w3bstream/pkg/modules/applet" 20 "github.com/machinefi/w3bstream/pkg/modules/config" 21 "github.com/machinefi/w3bstream/pkg/modules/event" 22 "github.com/machinefi/w3bstream/pkg/modules/publisher" 23 "github.com/machinefi/w3bstream/pkg/modules/transporter/mqtt" 24 "github.com/machinefi/w3bstream/pkg/types" 25 "github.com/machinefi/w3bstream/pkg/types/wasm" 26 ) 27 28 func GetBySFID(ctx context.Context, prj types.SFID) (*models.Project, error) { 29 d := types.MustMgrDBExecutorFromContext(ctx) 30 31 m := &models.Project{ 32 RelProject: models.RelProject{ProjectID: prj}, 33 } 34 if err := m.FetchByProjectID(d); err != nil { 35 if sqlx.DBErr(err).IsNotFound() { 36 return nil, status.ProjectNotFound 37 } 38 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 39 } 40 return m, nil 41 } 42 43 func GetByName(ctx context.Context, name string) (*models.Project, error) { 44 _, l := logr.Start(ctx, "project.GetByName") 45 defer l.End() 46 47 d := types.MustMgrDBExecutorFromContext(ctx) 48 m := &models.Project{ 49 ProjectName: models.ProjectName{Name: name}, 50 } 51 if err := m.FetchByName(d); err != nil { 52 if sqlx.DBErr(err).IsNotFound() { 53 return nil, status.ProjectNotFound 54 } 55 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 56 } 57 return m, nil 58 } 59 60 func GetDetail(ctx context.Context, prj *models.Project) (*Detail, error) { 61 rsp, err := applet.ListDetail(ctx, &applet.ListReq{ 62 CondArgs: applet.CondArgs{ProjectID: prj.ProjectID}, 63 }) 64 if err != nil { 65 return nil, err 66 } 67 68 return &Detail{ 69 ProjectID: prj.ProjectID, 70 ProjectName: prj.Name, 71 Applets: rsp.Data, 72 }, nil 73 } 74 75 func ListByCond(ctx context.Context, r *CondArgs) ([]models.Project, error) { 76 var ( 77 d = types.MustMgrDBExecutorFromContext(ctx) 78 prj = &models.Project{} 79 cond = r.Condition() 80 ) 81 82 data, err := prj.List(d, cond) 83 if err != nil { 84 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 85 } 86 return data, nil 87 } 88 89 func List(ctx context.Context, r *ListReq) (*ListRsp, error) { 90 var ( 91 err error 92 d = types.MustMgrDBExecutorFromContext(ctx) 93 prj = &models.Project{} 94 ret = &ListRsp{} 95 cond = r.Condition() 96 ) 97 98 ret.Data, err = prj.List(d, cond, r.Pager.Addition()) 99 if err != nil { 100 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 101 } 102 ret.Total, err = prj.Count(d, cond) 103 if err != nil { 104 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 105 } 106 107 return ret, nil 108 } 109 110 func ListDetail(ctx context.Context, r *ListReq) (*ListDetailRsp, error) { 111 ret := &ListDetailRsp{} 112 113 lst, err := List(ctx, r) 114 if err != nil { 115 return nil, err 116 } 117 ret.Total = lst.Total 118 119 for i := range lst.Data { 120 detail, err := GetDetail(ctx, &lst.Data[i]) 121 if err != nil { 122 return nil, err 123 } 124 ret.Data = append(ret.Data, detail) 125 } 126 return ret, nil 127 } 128 129 func Create(ctx context.Context, r *CreateReq) (*CreateRsp, error) { 130 ctx, l := logr.Start(ctx, "project.Create") 131 defer l.End() 132 133 d := types.MustMgrDBExecutorFromContext(ctx) 134 acc := types.MustAccountFromContext(ctx) 135 idg := confid.MustSFIDGeneratorFromContext(ctx) 136 137 prj := &models.Project{ 138 RelProject: models.RelProject{ProjectID: idg.MustGenSFID()}, 139 RelAccount: models.RelAccount{AccountID: acc.AccountID}, 140 ProjectName: models.ProjectName{Name: r.Name}, 141 ProjectBase: models.ProjectBase{ 142 Public: r.Public, 143 Version: r.Version, 144 Proto: r.Proto, 145 Description: r.Description, 146 }, 147 } 148 149 rsp := &CreateRsp{ 150 Project: prj, 151 } 152 153 err := sqlx.NewTasks(d).With( 154 func(d sqlx.DBExecutor) error { 155 if err := prj.Create(d); err != nil { 156 if sqlx.DBErr(err).IsConflict() { 157 return status.ProjectNameConflict 158 } 159 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 160 } 161 ctx = types.WithProject(ctx, prj) 162 return nil 163 }, 164 func(d sqlx.DBExecutor) error { 165 ctx := types.WithMgrDBExecutor(ctx, d) 166 if r.Env == nil { 167 r.Env = &wasm.Env{} 168 } 169 _, err := config.Create(ctx, prj.ProjectID, r.Env) 170 if err != nil { 171 return err 172 } 173 rsp.Env = r.Env 174 if r.Database == nil { 175 r.Database = &wasm.Database{} 176 } 177 _, err = config.Create(ctx, prj.ProjectID, r.Database) 178 if err != nil { 179 return err 180 } 181 rsp.Database = r.Database 182 if r.Flow == nil { 183 r.Flow = &wasm.Flow{} 184 } 185 _, err = config.Create(ctx, prj.ProjectID, r.Flow) 186 if err != nil { 187 return err 188 } 189 rsp.Flow = r.Flow 190 return nil 191 }, 192 func(d sqlx.DBExecutor) error { 193 if prj.Public == datatypes.TRUE { 194 if _, err := publisher.CreateAnonymousPublisher(ctx); err != nil { 195 return err 196 } 197 } 198 199 return nil 200 }, 201 ).Do() 202 if err != nil { 203 l.Error(err) 204 return nil, err 205 } 206 207 if err = mqtt.Subscribe(ctx, prj.Name); err != nil { 208 l.WithValues("prj", prj.Name).Warn(errors.Wrap(err, "channel create failed")) 209 } 210 rsp.ChannelState = datatypes.BooleanValue(err == nil) 211 212 filter, _ := types.ProjectFilterFromContext(ctx) 213 if filter != nil && filter.Filter(prj.ProjectID) { 214 sche := event.NewDefaultEventHandleScheduler(prj.ProjectID) 215 go sche.Run(ctx) 216 l.Info("event handler scheduler started") 217 } 218 219 return rsp, nil 220 } 221 222 func RemoveBySFID(ctx context.Context, id types.SFID) (err error) { 223 ctx, l := logr.Start(ctx, "project.RemoveBySFID", "project_id", id) 224 defer l.End() 225 226 var ( 227 d = types.MustMgrDBExecutorFromContext(ctx) 228 p *models.Project 229 ) 230 231 return sqlx.NewTasks(d).With( 232 func(d sqlx.DBExecutor) error { 233 ctx := types.WithMgrDBExecutor(ctx, d) 234 if p, err = GetBySFID(ctx, id); err != nil { 235 return err 236 } 237 mqtt.Stop(ctx, p.Name) 238 l = l.WithValues("project_name", p.Name) 239 l.Info("stop subscribing") 240 return nil 241 }, 242 func(d sqlx.DBExecutor) error { 243 if err := p.DeleteByProjectID(d); err != nil { 244 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 245 } 246 return nil 247 }, 248 func(d sqlx.DBExecutor) error { 249 ctx := types.WithMgrDBExecutor(ctx, d) 250 return config.Remove(ctx, &config.CondArgs{RelIDs: []types.SFID{p.ProjectID}}) 251 }, 252 func(d sqlx.DBExecutor) error { 253 ctx := types.WithMgrDBExecutor(ctx, d) 254 return applet.Remove(ctx, &applet.CondArgs{ProjectID: p.ProjectID}) 255 }, 256 ).Do() 257 } 258 259 func Init(ctx context.Context) ([]types.SFID, error) { 260 ctx, l := logger.NewSpanContext(ctx, "project.Init") 261 defer l.End() 262 263 d := types.MustMgrDBExecutorFromContext(ctx) 264 265 projects, err := (&models.Project{}).List(d, nil) 266 if err != nil { 267 return nil, err 268 } 269 270 fails := make([]string, 0, len(projects)) 271 succs := make([]types.SFID, 0, len(projects)) 272 273 defer func() { 274 // message := "" 275 // if len(fails) > 1 { 276 // message = "\nprojects failed to start:\n" 277 // message += strings.Join(fails, "\n") 278 // } 279 // if len(succs) > 1 { 280 // message = "\nstarted projects:\n" 281 // for _, v := range succs { 282 // message += v.String() + "\n" 283 // } 284 // } 285 // body, err := lark.Build(ctx, "Project Channel Monitoring", "INFO", message) 286 // if err != nil { 287 // return 288 // } 289 // _ = robot_notifier.Push(ctx, body) 290 }() 291 292 filter, _ := types.ProjectFilterFromContext(ctx) 293 294 l = l.WithValues("total", len(projects)) 295 for i := range projects { 296 v := &projects[i] 297 l := l.WithValues("prj", v.Name, "index", i) 298 if filter != nil && filter.Filter(v.ProjectID) { 299 sche := event.NewDefaultEventHandleScheduler(v.ProjectID) 300 go sche.Run(ctx) 301 l.Info("event handler scheduler started") 302 } 303 ctx = types.WithProject(ctx, v) 304 if err = mqtt.Subscribe(ctx, v.Name); err != nil { 305 err = errors.Errorf("%v: failed to subscribe mqtt %v", v.ProjectID, err) 306 fails = append(fails, err.Error()) 307 l.Warn(err) 308 continue 309 } 310 if v.Public == datatypes.TRUE && jwt.WithAnonymousPublisherFn == nil { 311 acc, err := account.GetAccountByAccountID(ctx, v.AccountID) 312 if err != nil { 313 err = errors.Errorf("%v: failed to get account %v %v", v.ProjectID, v.AccountID, err) 314 fails = append(fails, err.Error()) 315 l.Error(err) 316 continue 317 } 318 ctx = types.WithAccount(ctx, acc) 319 if _, err = publisher.CreateAnonymousPublisher(ctx); err != nil { 320 err = errors.Errorf("%v: failed to create publisher %v", v.ProjectID, err) 321 fails = append(fails, err.Error()) 322 l.Warn(errors.Wrap(err, "anonymous publisher create failed")) 323 } 324 } 325 succs = append(succs, v.ProjectID) 326 l.Info("start subscribe") 327 } 328 return succs, nil 329 }