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  }