diff --git a/go/api/subreddits_create.go b/go/api/subreddits_create.go new file mode 100644 index 0000000..980b969 --- /dev/null +++ b/go/api/subreddits_create.go @@ -0,0 +1,38 @@ +package api + +import ( + "context" + "errors" + + "connectrpc.com/connect" + "github.com/aarondl/opt/omit" + "github.com/mattn/go-sqlite3" + "github.com/tigorlazuardi/bluemage/go/gen/models" + "github.com/tigorlazuardi/bluemage/go/pkg/errs" +) + +func (api *API) SubredditCreate(ctx context.Context, subreddit *models.Subreddit) (err error) { + // TODO: add check to Reddit API to see if subreddit exists. + + api.lockf(func() { + _, err = models.Subreddits.Insert(ctx, api.DB, &models.SubredditSetter{ + Name: omit.From(subreddit.Name), + DisableScheduler: omit.From(subreddit.DisableScheduler), + Type: omit.From(subreddit.Type), + Schedule: omit.From(subreddit.Schedule), + Countback: omit.From(subreddit.Countback), + }) + }) + if err != nil { + if sqlite3err := new(sqlite3.Error); errors.As(err, &sqlite3err) { + if sqlite3err.Code == sqlite3.ErrConstraint { + return errs. + Wrapw(err, "subreddit already exists", "input", subreddit). + Code(connect.CodeAlreadyExists) + } + } + return errs.Wrap(err, "failed to insert subreddit") + } + + return nil +} diff --git a/go/converts/converter.go b/go/converts/converter.go index 2ce9444..9a652db 100644 --- a/go/converts/converter.go +++ b/go/converts/converter.go @@ -1,4 +1,4 @@ -package converter +package converts import ( "github.com/aarondl/opt/omit" diff --git a/go/converts/subreddit.go b/go/converts/subreddit.go new file mode 100644 index 0000000..1901920 --- /dev/null +++ b/go/converts/subreddit.go @@ -0,0 +1,21 @@ +package converts + +import ( + "github.com/tigorlazuardi/bluemage/go/gen/models" + subreddits "github.com/tigorlazuardi/bluemage/go/gen/proto/subreddits/v1" +) + +// goverter:converter +// goverter:extend BoolToInt8 SubredditTypeToString Int8ToBool PtrBoolToOmitInt8 +// goverter:extend PtrStringToOmitString PtrFloat64ToOmitFloat64 PtrInt32ToOmitInt32 +type SubredditConverter interface { + // goverter:ignore CreatedAt UpdatedAt R CoverImageID + CreateSubredditRequestToModelsSubreddit(*subreddits.CreateSubredditRequest) *models.Subreddit +} + +func SubredditTypeToString(subType subreddits.SubredditType) string { + if subType == subreddits.SubredditType_SUBREDDIT_TYPE_USER { + return "user" + } + return "r" +} diff --git a/go/server/device_handlers.go b/go/server/device_handlers.go index e012aee..71f9135 100644 --- a/go/server/device_handlers.go +++ b/go/server/device_handlers.go @@ -19,16 +19,16 @@ type DeviceHandler struct { v1connect.UnimplementedDeviceServiceHandler } -var convert converter.DeviceConverterImpl +var deviceConvert converter.DeviceConverterImpl // CreateDevice implements v1connect.DeviceServiceHandler. func (d *DeviceHandler) CreateDevice(ctx context.Context, request *connect.Request[device.CreateDeviceRequest]) (*connect.Response[device.CreateDeviceResponse], error) { - dev := convert.CreateDeviceRequestToModelsDevice(request.Msg) + dev := deviceConvert.CreateDeviceRequestToModelsDevice(request.Msg) dev, err := d.API.DevicesCreate(ctx, dev) if err != nil { return nil, errs.IntoConnectError(err) } - devResp := convert.ModelsDeviceToCreateDeviceResponse(dev) + devResp := deviceConvert.ModelsDeviceToCreateDeviceResponse(dev) return connect.NewResponse(devResp), nil } @@ -39,7 +39,7 @@ func (d *DeviceHandler) GetDevice(ctx context.Context, request *connect.Request[ return nil, errs.IntoConnectError(err) } - devResp := convert.ModelsDeviceToGetDeviceResponse(dev) + devResp := deviceConvert.ModelsDeviceToGetDeviceResponse(dev) return connect.NewResponse(devResp), nil } @@ -56,7 +56,7 @@ func (d *DeviceHandler) ListDevices(ctx context.Context, request *connect.Reques // Only fields that are set in the request will be updated. func (de *DeviceHandler) UpdateDevice(ctx context.Context, request *connect.Request[device.UpdateDeviceRequest]) (*connect.Response[device.UpdateDeviceResponse], error) { slug := request.Msg.Slug - set := convert.DeviceSetterProtoToModelsDeviceSetter(request.Msg.Set) + set := deviceConvert.DeviceSetterProtoToModelsDeviceSetter(request.Msg.Set) err := de.API.DevicesUpdate(ctx, slug, set) if err != nil { return nil, errs.IntoConnectError(err) diff --git a/go/server/subreddit_handlers.go b/go/server/subreddit_handlers.go new file mode 100644 index 0000000..0905a80 --- /dev/null +++ b/go/server/subreddit_handlers.go @@ -0,0 +1,66 @@ +package server + +import ( + "context" + + "connectrpc.com/connect" + "github.com/tigorlazuardi/bluemage/go/api" + subreddits "github.com/tigorlazuardi/bluemage/go/gen/proto/subreddits/v1" + "github.com/tigorlazuardi/bluemage/go/gen/proto/subreddits/v1/v1connect" +) + +type SubredditHandler struct { + API *api.API + + v1connect.UnimplementedSubredditsServiceHandler +} + +// CreateSubreddit creates a new subreddit +// Returns the subreddit name. +// +// Returns the following error codes: +// - connect.CodeAlreadyExists if subreddit with the same name already exists. +// - connect.CodeInvalidArgument if validation failed, e.g. Invalid schedule cron format. +// - connect.CodeNotFound if the subreddit does not exist. +func (su *SubredditHandler) CreateSubreddit(ctx context.Context, request *connect.Request[subreddits.CreateSubredditRequest]) (*connect.Response[subreddits.CreateSubredditResponse], error) { + panic("not implemented") // TODO: Implement +} + +// GetSubreddit returns a subreddit by its name. +// +// Returns error with connect.CodeNotFound if subreddit does not exist. +func (su *SubredditHandler) GetSubreddit(_ context.Context, _ *connect.Request[subreddits.GetSubredditRequest]) (*connect.Response[subreddits.GetSubredditResponse], error) { + panic("not implemented") // TODO: Implement +} + +// ListSubreddits returns a list of subreddits. +func (su *SubredditHandler) ListSubreddits(_ context.Context, _ *connect.Request[subreddits.ListSubredditsRequest]) (*connect.Response[subreddits.ListSubredditsResponse], error) { + panic("not implemented") // TODO: Implement +} + +// UpdateSubreddit updates a subreddit. +// +// Only the fields that are set in the request will be updated. +// +// Returns error with connect.CodeNotFound if subreddit does not exist. +func (su *SubredditHandler) UpdateSubreddit(_ context.Context, _ *connect.Request[subreddits.UpdateSubredditRequest]) (*connect.Response[subreddits.UpdateSubredditResponse], error) { + panic("not implemented") // TODO: Implement +} + +// DeleteSubreddit deletes a subreddit. +// +// Returns error with connect.CodeNotFound if subreddit does not exist. +func (su *SubredditHandler) DeleteSubreddit(_ context.Context, _ *connect.Request[subreddits.DeleteSubredditRequest]) (*connect.Response[subreddits.DeleteSubredditResponse], error) { + panic("not implemented") // TODO: Implement +} + +// ResolveSubredditName resolves the given subreddit name. +// +// The returned resolved_name is the name that is actually +// used in Reddit. +// +// Returns error with connect.CodeNotFound if subreddit does not exist. +// So this rpc endpoint also acts to check subreddit's existence. +func (su *SubredditHandler) ResolveSubredditName(_ context.Context, _ *connect.Request[subreddits.ResolveSubredditNameRequest]) (*connect.Response[subreddits.ResolveSubredditNameResponse], error) { + panic("not implemented") // TODO: Implement +} diff --git a/schemas/proto/subreddits/v1/create.proto b/schemas/proto/subreddits/v1/create.proto index e8cc75d..4526cad 100644 --- a/schemas/proto/subreddits/v1/create.proto +++ b/schemas/proto/subreddits/v1/create.proto @@ -18,7 +18,7 @@ message CreateSubredditRequest { // schedule is cron job spec to set schedule on when // the runner for this subreddit runs. string schedule = 4 [(buf.validate.field).string.min_len = 1]; - uint32 countback = 5 [(buf.validate.field).uint32.gt = 0]; + int64 countback = 5 [(buf.validate.field).int64.gt = 0]; } enum SubredditType {