diff --git a/.env.ci b/.env.ci index b9a0df5e..500ca24e 100644 --- a/.env.ci +++ b/.env.ci @@ -35,3 +35,5 @@ CLIENT_REGISTRATION_URL=http://localhost:8080/registration SMTP_ADDR=smtp:1025 EMAIL_FROM=noreply@kurume-nct.com + +GITHUB_ACCESS_TOKEN=yourAccessToken diff --git a/.env.sample b/.env.sample index 50c4c02b..54040a7e 100644 --- a/.env.sample +++ b/.env.sample @@ -36,3 +36,5 @@ CLIENT_REGISTRATION_URL=http://localhost:8080/registration SMTP_ADDR=smtp:1025 EMAIL_FROM=noreply@kurume-nct.com + +GITHUB_ACCESS_TOKEN=yourAccessToken diff --git a/Gopkg.lock b/Gopkg.lock index d09c1bdf..df67ae16 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -645,6 +645,26 @@ pruneopts = "" revision = "a683aaf2d516deecd70cad0c72e3ca773ecfcef0" +[[projects]] + branch = "master" + digest = "1:c69eea49213a8eb18fc63b71e842bcf859fb2f42752acb4c69cd1dad76cee9ad" + name = "github.com/shurcooL/githubv4" + packages = ["."] + pruneopts = "" + revision = "d9689b59501712371c702f54e9573531888d52fe" + +[[projects]] + branch = "master" + digest = "1:78e45028a7525b961abef663e19f631dd179fde7e11c7c6eeb57a778e490c916" + name = "github.com/shurcooL/graphql" + packages = [ + ".", + "ident", + "internal/jsonutil", + ] + pruneopts = "" + revision = "d48a9a75455f6af30244670bc0c9d0e38e7392b5" + [[projects]] branch = "master" digest = "1:c756d63fd19d7a0addad30ecb77014fbe82b428c34bdd4142177d0a3d8e77095" @@ -1089,6 +1109,7 @@ "github.com/ory/hydra/sdk/go/hydra", "github.com/ory/hydra/sdk/go/hydra/swagger", "github.com/pkg/errors", + "github.com/shurcooL/githubv4", "github.com/spf13/viper", "github.com/volatiletech/null", "github.com/volatiletech/sqlboiler", @@ -1104,6 +1125,7 @@ "golang.org/x/crypto/bcrypt", "golang.org/x/crypto/ssh/terminal", "golang.org/x/net/context", + "golang.org/x/oauth2", "google.golang.org/genproto/googleapis/api/annotations", "google.golang.org/grpc", "google.golang.org/grpc/codes", diff --git a/api/contribution_conllections.pb.go b/api/contribution_conllections.pb.go new file mode 100644 index 00000000..27045128 --- /dev/null +++ b/api/contribution_conllections.pb.go @@ -0,0 +1,320 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: contribution_conllections.proto + +package api_pb // import "github.com/ProgrammingLab/prolab-accounts/api" + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/golang/protobuf/ptypes/empty" +import timestamp "github.com/golang/protobuf/ptypes/timestamp" +import _ "github.com/mwitkow/go-proto-validators" +import _ "google.golang.org/genproto/googleapis/api/annotations" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type ContributionConllection struct { + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + TotalCount int32 `protobuf:"varint,2,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"` + Days []*ContributionDay `protobuf:"bytes,3,rep,name=days,proto3" json:"days,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ContributionConllection) Reset() { *m = ContributionConllection{} } +func (m *ContributionConllection) String() string { return proto.CompactTextString(m) } +func (*ContributionConllection) ProtoMessage() {} +func (*ContributionConllection) Descriptor() ([]byte, []int) { + return fileDescriptor_contribution_conllections_4e54b473808af436, []int{0} +} +func (m *ContributionConllection) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ContributionConllection.Unmarshal(m, b) +} +func (m *ContributionConllection) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ContributionConllection.Marshal(b, m, deterministic) +} +func (dst *ContributionConllection) XXX_Merge(src proto.Message) { + xxx_messageInfo_ContributionConllection.Merge(dst, src) +} +func (m *ContributionConllection) XXX_Size() int { + return xxx_messageInfo_ContributionConllection.Size(m) +} +func (m *ContributionConllection) XXX_DiscardUnknown() { + xxx_messageInfo_ContributionConllection.DiscardUnknown(m) +} + +var xxx_messageInfo_ContributionConllection proto.InternalMessageInfo + +func (m *ContributionConllection) GetUser() *User { + if m != nil { + return m.User + } + return nil +} + +func (m *ContributionConllection) GetTotalCount() int32 { + if m != nil { + return m.TotalCount + } + return 0 +} + +func (m *ContributionConllection) GetDays() []*ContributionDay { + if m != nil { + return m.Days + } + return nil +} + +type ContributionDay struct { + Date *timestamp.Timestamp `protobuf:"bytes,1,opt,name=date,proto3" json:"date,omitempty"` + Count int32 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ContributionDay) Reset() { *m = ContributionDay{} } +func (m *ContributionDay) String() string { return proto.CompactTextString(m) } +func (*ContributionDay) ProtoMessage() {} +func (*ContributionDay) Descriptor() ([]byte, []int) { + return fileDescriptor_contribution_conllections_4e54b473808af436, []int{1} +} +func (m *ContributionDay) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ContributionDay.Unmarshal(m, b) +} +func (m *ContributionDay) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ContributionDay.Marshal(b, m, deterministic) +} +func (dst *ContributionDay) XXX_Merge(src proto.Message) { + xxx_messageInfo_ContributionDay.Merge(dst, src) +} +func (m *ContributionDay) XXX_Size() int { + return xxx_messageInfo_ContributionDay.Size(m) +} +func (m *ContributionDay) XXX_DiscardUnknown() { + xxx_messageInfo_ContributionDay.DiscardUnknown(m) +} + +var xxx_messageInfo_ContributionDay proto.InternalMessageInfo + +func (m *ContributionDay) GetDate() *timestamp.Timestamp { + if m != nil { + return m.Date + } + return nil +} + +func (m *ContributionDay) GetCount() int32 { + if m != nil { + return m.Count + } + return 0 +} + +type ListContributionConllectionsRequest struct { + UsersCount int32 `protobuf:"varint,1,opt,name=users_count,json=usersCount,proto3" json:"users_count,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListContributionConllectionsRequest) Reset() { *m = ListContributionConllectionsRequest{} } +func (m *ListContributionConllectionsRequest) String() string { return proto.CompactTextString(m) } +func (*ListContributionConllectionsRequest) ProtoMessage() {} +func (*ListContributionConllectionsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_contribution_conllections_4e54b473808af436, []int{2} +} +func (m *ListContributionConllectionsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListContributionConllectionsRequest.Unmarshal(m, b) +} +func (m *ListContributionConllectionsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListContributionConllectionsRequest.Marshal(b, m, deterministic) +} +func (dst *ListContributionConllectionsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListContributionConllectionsRequest.Merge(dst, src) +} +func (m *ListContributionConllectionsRequest) XXX_Size() int { + return xxx_messageInfo_ListContributionConllectionsRequest.Size(m) +} +func (m *ListContributionConllectionsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ListContributionConllectionsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ListContributionConllectionsRequest proto.InternalMessageInfo + +func (m *ListContributionConllectionsRequest) GetUsersCount() int32 { + if m != nil { + return m.UsersCount + } + return 0 +} + +type ListContributionConllectionsResponse struct { + ContributionConllections []*ContributionConllection `protobuf:"bytes,1,rep,name=contribution_conllections,json=contributionConllections,proto3" json:"contribution_conllections,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListContributionConllectionsResponse) Reset() { *m = ListContributionConllectionsResponse{} } +func (m *ListContributionConllectionsResponse) String() string { return proto.CompactTextString(m) } +func (*ListContributionConllectionsResponse) ProtoMessage() {} +func (*ListContributionConllectionsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_contribution_conllections_4e54b473808af436, []int{3} +} +func (m *ListContributionConllectionsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListContributionConllectionsResponse.Unmarshal(m, b) +} +func (m *ListContributionConllectionsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListContributionConllectionsResponse.Marshal(b, m, deterministic) +} +func (dst *ListContributionConllectionsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListContributionConllectionsResponse.Merge(dst, src) +} +func (m *ListContributionConllectionsResponse) XXX_Size() int { + return xxx_messageInfo_ListContributionConllectionsResponse.Size(m) +} +func (m *ListContributionConllectionsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ListContributionConllectionsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ListContributionConllectionsResponse proto.InternalMessageInfo + +func (m *ListContributionConllectionsResponse) GetContributionConllections() []*ContributionConllection { + if m != nil { + return m.ContributionConllections + } + return nil +} + +func init() { + proto.RegisterType((*ContributionConllection)(nil), "programming_lab.prolab_accounts.ContributionConllection") + proto.RegisterType((*ContributionDay)(nil), "programming_lab.prolab_accounts.ContributionDay") + proto.RegisterType((*ListContributionConllectionsRequest)(nil), "programming_lab.prolab_accounts.ListContributionConllectionsRequest") + proto.RegisterType((*ListContributionConllectionsResponse)(nil), "programming_lab.prolab_accounts.ListContributionConllectionsResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// ContributionConllectionServiceClient is the client API for ContributionConllectionService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ContributionConllectionServiceClient interface { + ListContributionConllections(ctx context.Context, in *ListContributionConllectionsRequest, opts ...grpc.CallOption) (*ListContributionConllectionsResponse, error) +} + +type contributionConllectionServiceClient struct { + cc *grpc.ClientConn +} + +func NewContributionConllectionServiceClient(cc *grpc.ClientConn) ContributionConllectionServiceClient { + return &contributionConllectionServiceClient{cc} +} + +func (c *contributionConllectionServiceClient) ListContributionConllections(ctx context.Context, in *ListContributionConllectionsRequest, opts ...grpc.CallOption) (*ListContributionConllectionsResponse, error) { + out := new(ListContributionConllectionsResponse) + err := c.cc.Invoke(ctx, "/programming_lab.prolab_accounts.ContributionConllectionService/ListContributionConllections", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ContributionConllectionServiceServer is the server API for ContributionConllectionService service. +type ContributionConllectionServiceServer interface { + ListContributionConllections(context.Context, *ListContributionConllectionsRequest) (*ListContributionConllectionsResponse, error) +} + +func RegisterContributionConllectionServiceServer(s *grpc.Server, srv ContributionConllectionServiceServer) { + s.RegisterService(&_ContributionConllectionService_serviceDesc, srv) +} + +func _ContributionConllectionService_ListContributionConllections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListContributionConllectionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ContributionConllectionServiceServer).ListContributionConllections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/programming_lab.prolab_accounts.ContributionConllectionService/ListContributionConllections", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ContributionConllectionServiceServer).ListContributionConllections(ctx, req.(*ListContributionConllectionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ContributionConllectionService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "programming_lab.prolab_accounts.ContributionConllectionService", + HandlerType: (*ContributionConllectionServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListContributionConllections", + Handler: _ContributionConllectionService_ListContributionConllections_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "contribution_conllections.proto", +} + +func init() { + proto.RegisterFile("contribution_conllections.proto", fileDescriptor_contribution_conllections_4e54b473808af436) +} + +var fileDescriptor_contribution_conllections_4e54b473808af436 = []byte{ + // 463 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0x41, 0x8a, 0xd4, 0x40, + 0x14, 0xa5, 0x66, 0xa6, 0x5d, 0x54, 0x23, 0x6a, 0x21, 0x18, 0x63, 0x63, 0x9a, 0xa8, 0xd0, 0x9b, + 0x4e, 0xa4, 0x95, 0x41, 0x71, 0x37, 0xdd, 0xee, 0x66, 0x21, 0x51, 0x11, 0xdd, 0x84, 0x4a, 0xa6, + 0x8c, 0x85, 0x49, 0xfd, 0xb2, 0xea, 0x67, 0x86, 0xde, 0x7a, 0x05, 0xd7, 0x9e, 0xc4, 0x43, 0xb8, + 0xf0, 0x00, 0x82, 0x78, 0x02, 0x2f, 0xa0, 0xa4, 0x92, 0x76, 0xc2, 0x40, 0xa6, 0x95, 0xc9, 0x2a, + 0xf5, 0xdf, 0xff, 0xef, 0xfd, 0xf7, 0xa8, 0xa2, 0x41, 0x0e, 0x0a, 0x8d, 0xcc, 0x6a, 0x94, 0xa0, + 0xd2, 0x1c, 0x54, 0x59, 0x8a, 0xbc, 0xf9, 0xb7, 0x91, 0x36, 0x80, 0xc0, 0x02, 0x6d, 0xa0, 0x30, + 0xbc, 0xaa, 0xa4, 0x2a, 0xd2, 0x92, 0x67, 0x4d, 0xb9, 0xe4, 0x59, 0xca, 0xf3, 0x1c, 0x6a, 0x85, + 0xd6, 0xdf, 0x2f, 0x24, 0xbe, 0xab, 0xb3, 0x28, 0x87, 0x2a, 0xae, 0x4e, 0x24, 0xbe, 0x87, 0x93, + 0xb8, 0x80, 0xb9, 0x9b, 0x9e, 0x1f, 0xf3, 0x52, 0x1e, 0x71, 0x04, 0x63, 0xe3, 0xbf, 0xbf, 0x2d, + 0xb1, 0x3f, 0x29, 0x00, 0x8a, 0x52, 0xc4, 0x5c, 0xcb, 0x98, 0x2b, 0x05, 0xc8, 0x7b, 0xb2, 0xfe, + 0xad, 0x0e, 0x75, 0xa7, 0xac, 0x7e, 0x1b, 0x8b, 0x4a, 0xe3, 0xba, 0x03, 0x83, 0xb3, 0x20, 0xca, + 0x4a, 0x58, 0xe4, 0x95, 0xee, 0x1a, 0xc6, 0xb5, 0x15, 0xa6, 0xa3, 0x0a, 0xbf, 0x10, 0x7a, 0x63, + 0xd9, 0x73, 0xb9, 0x3c, 0x35, 0xc9, 0x1e, 0xd3, 0xbd, 0xa6, 0xd5, 0x23, 0x53, 0x32, 0x1b, 0x2f, + 0xee, 0x45, 0x5b, 0xcc, 0x46, 0x2f, 0xad, 0x30, 0x89, 0x1b, 0x61, 0x01, 0x1d, 0x23, 0x20, 0x2f, + 0x53, 0x07, 0x79, 0x3b, 0x53, 0x32, 0x1b, 0x25, 0xd4, 0x95, 0x96, 0x4d, 0x85, 0xad, 0xe8, 0xde, + 0x11, 0x5f, 0x5b, 0x6f, 0x77, 0xba, 0x3b, 0x1b, 0x2f, 0xee, 0x6f, 0xe5, 0xee, 0xef, 0xb8, 0xe2, + 0xeb, 0xc4, 0x4d, 0x87, 0xaf, 0xe8, 0x95, 0x33, 0x00, 0x8b, 0x1a, 0x62, 0x14, 0xdd, 0xd2, 0x7e, + 0xd4, 0xa6, 0x11, 0x6d, 0xd2, 0x88, 0x5e, 0x6c, 0xd2, 0x48, 0x5c, 0x1f, 0xbb, 0x4e, 0x47, 0xfd, + 0x1d, 0xdb, 0x43, 0xf8, 0x9a, 0xde, 0x39, 0x94, 0x16, 0x07, 0x92, 0xb1, 0x89, 0xf8, 0x50, 0x0b, + 0x8b, 0x6c, 0x41, 0xdb, 0x30, 0x3b, 0x9b, 0x8d, 0xe6, 0xe8, 0xe0, 0xda, 0x8f, 0xef, 0xc1, 0xe5, + 0xab, 0xbf, 0x37, 0x1f, 0xf1, 0x44, 0x42, 0x5d, 0x97, 0x73, 0x1e, 0x7e, 0x26, 0xf4, 0xee, 0xf9, + 0xdc, 0x56, 0x83, 0xb2, 0x82, 0xd5, 0xf4, 0xe6, 0xe0, 0xfd, 0xf3, 0x88, 0xcb, 0xed, 0xd1, 0x7f, + 0xe5, 0xd6, 0x53, 0x49, 0xbc, 0x7c, 0x40, 0x7e, 0xf1, 0x8b, 0xd0, 0xdb, 0x03, 0x53, 0xcf, 0x85, + 0x39, 0x96, 0xb9, 0x60, 0x5f, 0x09, 0x9d, 0x9c, 0x67, 0x81, 0xad, 0xb6, 0xee, 0xf5, 0x0f, 0xe9, + 0xfa, 0x4f, 0x2f, 0xc8, 0xd2, 0xe6, 0x18, 0x86, 0x1f, 0xbf, 0xfd, 0xfc, 0xb4, 0x33, 0x61, 0x7e, + 0x3c, 0x18, 0xe7, 0xc1, 0xfe, 0x9b, 0x87, 0xbd, 0x97, 0xfa, 0xec, 0x54, 0xf6, 0x90, 0x67, 0x71, + 0xab, 0x3a, 0xdf, 0xa8, 0x36, 0x8f, 0xf2, 0x09, 0xd7, 0x32, 0xd5, 0x59, 0x76, 0xc9, 0xdd, 0xab, + 0x07, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd7, 0x1c, 0xb6, 0xe8, 0x2a, 0x04, 0x00, 0x00, +} diff --git a/api/contribution_conllections.pb.gw.go b/api/contribution_conllections.pb.gw.go new file mode 100644 index 00000000..13cd7978 --- /dev/null +++ b/api/contribution_conllections.pb.gw.go @@ -0,0 +1,124 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: contribution_conllections.proto + +/* +Package api_pb is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package api_pb + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray + +var ( + filter_ContributionConllectionService_ListContributionConllections_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_ContributionConllectionService_ListContributionConllections_0(ctx context.Context, marshaler runtime.Marshaler, client ContributionConllectionServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListContributionConllectionsRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ContributionConllectionService_ListContributionConllections_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListContributionConllections(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterContributionConllectionServiceHandlerFromEndpoint is same as RegisterContributionConllectionServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterContributionConllectionServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterContributionConllectionServiceHandler(ctx, mux, conn) +} + +// RegisterContributionConllectionServiceHandler registers the http handlers for service ContributionConllectionService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterContributionConllectionServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterContributionConllectionServiceHandlerClient(ctx, mux, NewContributionConllectionServiceClient(conn)) +} + +// RegisterContributionConllectionServiceHandlerClient registers the http handlers for service ContributionConllectionService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ContributionConllectionServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ContributionConllectionServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "ContributionConllectionServiceClient" to call the correct interceptors. +func RegisterContributionConllectionServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ContributionConllectionServiceClient) error { + + mux.Handle("GET", pattern_ContributionConllectionService_ListContributionConllections_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ContributionConllectionService_ListContributionConllections_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_ContributionConllectionService_ListContributionConllections_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_ContributionConllectionService_ListContributionConllections_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"contribution_conllections"}, "")) +) + +var ( + forward_ContributionConllectionService_ListContributionConllections_0 = runtime.ForwardResponseMessage +) diff --git a/api/contribution_conllections.swagger.json b/api/contribution_conllections.swagger.json new file mode 100644 index 00000000..59504537 --- /dev/null +++ b/api/contribution_conllections.swagger.json @@ -0,0 +1,170 @@ +{ + "swagger": "2.0", + "info": { + "title": "contribution_conllections.proto", + "version": "version not set" + }, + "schemes": [ + "http", + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/contribution_conllections": { + "get": { + "operationId": "ListContributionConllections", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/prolab_accountsListContributionConllectionsResponse" + } + } + }, + "parameters": [ + { + "name": "users_count", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "tags": [ + "ContributionConllectionService" + ] + } + } + }, + "definitions": { + "prolab_accountsContributionConllection": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/prolab_accountsUser" + }, + "total_count": { + "type": "integer", + "format": "int32" + }, + "days": { + "type": "array", + "items": { + "$ref": "#/definitions/prolab_accountsContributionDay" + } + } + } + }, + "prolab_accountsContributionDay": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date-time" + }, + "count": { + "type": "integer", + "format": "int32" + } + } + }, + "prolab_accountsDepartment": { + "type": "object", + "properties": { + "department_id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "short_name": { + "type": "string" + } + } + }, + "prolab_accountsListContributionConllectionsResponse": { + "type": "object", + "properties": { + "contribution_conllections": { + "type": "array", + "items": { + "$ref": "#/definitions/prolab_accountsContributionConllection" + } + } + } + }, + "prolab_accountsProfileScope": { + "type": "string", + "enum": [ + "MEMBERS_ONLY", + "PUBLIC" + ], + "default": "MEMBERS_ONLY" + }, + "prolab_accountsRole": { + "type": "object", + "properties": { + "role_id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "prolab_accountsUser": { + "type": "object", + "properties": { + "user_id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "grade": { + "type": "integer", + "format": "int32" + }, + "left": { + "type": "boolean", + "format": "boolean" + }, + "role": { + "$ref": "#/definitions/prolab_accountsRole" + }, + "twitter_screen_name": { + "type": "string" + }, + "github_user_name": { + "type": "string" + }, + "department": { + "$ref": "#/definitions/prolab_accountsDepartment" + }, + "profile_scope": { + "$ref": "#/definitions/prolab_accountsProfileScope" + } + } + } + } +} diff --git a/api/contribution_conllections.validator.pb.go b/api/contribution_conllections.validator.pb.go new file mode 100644 index 00000000..89a34f54 --- /dev/null +++ b/api/contribution_conllections.validator.pb.go @@ -0,0 +1,63 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: contribution_conllections.proto + +package api_pb + +import ( + fmt "fmt" + math "math" + proto "github.com/golang/protobuf/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + _ "github.com/golang/protobuf/ptypes/empty" + _ "github.com/golang/protobuf/ptypes/timestamp" + _ "github.com/mwitkow/go-proto-validators" + github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +func (this *ContributionConllection) Validate() error { + if this.User != nil { + if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.User); err != nil { + return github_com_mwitkow_go_proto_validators.FieldError("User", err) + } + } + for _, item := range this.Days { + if item != nil { + if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(item); err != nil { + return github_com_mwitkow_go_proto_validators.FieldError("Days", err) + } + } + } + return nil +} +func (this *ContributionDay) Validate() error { + if this.Date != nil { + if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.Date); err != nil { + return github_com_mwitkow_go_proto_validators.FieldError("Date", err) + } + } + return nil +} +func (this *ListContributionConllectionsRequest) Validate() error { + if !(this.UsersCount > -1) { + return github_com_mwitkow_go_proto_validators.FieldError("UsersCount", fmt.Errorf(`value '%v' must be greater than '-1'`, this.UsersCount)) + } + if !(this.UsersCount < 101) { + return github_com_mwitkow_go_proto_validators.FieldError("UsersCount", fmt.Errorf(`value '%v' must be less than '101'`, this.UsersCount)) + } + return nil +} +func (this *ListContributionConllectionsResponse) Validate() error { + for _, item := range this.ContributionConllections { + if item != nil { + if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(item); err != nil { + return github_com_mwitkow_go_proto_validators.FieldError("ContributionConllections", err) + } + } + } + return nil +} diff --git a/api/entries.validator.pb.go b/api/entries.validator.pb.go index 5dcc7a1e..035daa5d 100644 --- a/api/entries.validator.pb.go +++ b/api/entries.validator.pb.go @@ -7,10 +7,10 @@ import ( fmt "fmt" math "math" proto "github.com/golang/protobuf/proto" - _ "github.com/mwitkow/go-proto-validators" - _ "google.golang.org/genproto/googleapis/api/annotations" _ "github.com/golang/protobuf/ptypes/empty" _ "github.com/golang/protobuf/ptypes/timestamp" + _ "github.com/mwitkow/go-proto-validators" + _ "google.golang.org/genproto/googleapis/api/annotations" github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" ) diff --git a/api/invitations.validator.pb.go b/api/invitations.validator.pb.go index 2d8f7281..327e05cc 100644 --- a/api/invitations.validator.pb.go +++ b/api/invitations.validator.pb.go @@ -7,8 +7,8 @@ import ( fmt "fmt" math "math" proto "github.com/golang/protobuf/proto" - _ "google.golang.org/genproto/googleapis/api/annotations" _ "github.com/golang/protobuf/ptypes/empty" + _ "google.golang.org/genproto/googleapis/api/annotations" github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" ) diff --git a/api/oauth.validator.pb.go b/api/oauth.validator.pb.go index 3c2b14e8..638581b8 100644 --- a/api/oauth.validator.pb.go +++ b/api/oauth.validator.pb.go @@ -7,9 +7,9 @@ import ( fmt "fmt" math "math" proto "github.com/golang/protobuf/proto" - _ "github.com/ProgrammingLab/prolab-accounts/api/type" _ "google.golang.org/genproto/googleapis/api/annotations" _ "github.com/golang/protobuf/ptypes/empty" + _ "github.com/ProgrammingLab/prolab-accounts/api/type" github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" ) diff --git a/api/protos/contribution_conllections.proto b/api/protos/contribution_conllections.proto new file mode 100644 index 00000000..050aa022 --- /dev/null +++ b/api/protos/contribution_conllections.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package programming_lab.prolab_accounts; + +option go_package = "github.com/ProgrammingLab/prolab-accounts/api;api_pb"; + + +import "github.com/mwitkow/go-proto-validators/validator.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; + +import "users.proto"; + +service ContributionConllectionService { + rpc ListContributionConllections (ListContributionConllectionsRequest) returns (ListContributionConllectionsResponse) { + option (google.api.http) = { + get: "/contribution_conllections" + }; + } +} + +message ContributionConllection { + User user = 1; + int32 total_count = 2; + repeated ContributionDay days = 3; +} + +message ContributionDay { + google.protobuf.Timestamp date = 1; + int32 count = 2; +} + +message ListContributionConllectionsRequest { + int32 users_count = 1 [(validator.field) = {int_gt: -1, int_lt: 101}]; +} + +message ListContributionConllectionsResponse { + repeated ContributionConllection contribution_conllections = 1; +} diff --git a/api/roles.validator.pb.go b/api/roles.validator.pb.go index 4ebbdaac..3b6aedb8 100644 --- a/api/roles.validator.pb.go +++ b/api/roles.validator.pb.go @@ -7,8 +7,8 @@ import ( fmt "fmt" math "math" proto "github.com/golang/protobuf/proto" - _ "google.golang.org/genproto/googleapis/api/annotations" _ "github.com/golang/protobuf/ptypes/empty" + _ "google.golang.org/genproto/googleapis/api/annotations" github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" ) diff --git a/api/user_blogs.validator.pb.go b/api/user_blogs.validator.pb.go index 45d2b887..a0eaead4 100644 --- a/api/user_blogs.validator.pb.go +++ b/api/user_blogs.validator.pb.go @@ -7,8 +7,8 @@ import ( fmt "fmt" math "math" proto "github.com/golang/protobuf/proto" - _ "github.com/golang/protobuf/ptypes/empty" _ "google.golang.org/genproto/googleapis/api/annotations" + _ "github.com/golang/protobuf/ptypes/empty" github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" ) diff --git a/api/users.validator.pb.go b/api/users.validator.pb.go index 89efca3e..63fd31db 100644 --- a/api/users.validator.pb.go +++ b/api/users.validator.pb.go @@ -7,9 +7,9 @@ import ( fmt "fmt" math "math" proto "github.com/golang/protobuf/proto" - _ "github.com/golang/protobuf/ptypes/empty" _ "github.com/mwitkow/go-proto-validators" _ "google.golang.org/genproto/googleapis/api/annotations" + _ "github.com/golang/protobuf/ptypes/empty" regexp "regexp" github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" ) diff --git a/app/config/config.go b/app/config/config.go index 5efe2e77..f83109d9 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -22,6 +22,7 @@ type Config struct { ClientRegistrationURL string `envconfig:"client_registration_url" required:"true"` SMTPAddr string `envconfig:"smtp_addr" required:"true"` EmailFrom string `envconfig:"email_from" required:"true"` + GitHubAccessToken string `envconfig:"github_access_token" required:"true"` } // LoadConfig loads config diff --git a/app/di/store_component.go b/app/di/store_component.go index b770b45c..1a7c3f33 100644 --- a/app/di/store_component.go +++ b/app/di/store_component.go @@ -14,6 +14,7 @@ import ( departmentstore "github.com/ProgrammingLab/prolab-accounts/infra/store/department" entrystore "github.com/ProgrammingLab/prolab-accounts/infra/store/entry" feedstore "github.com/ProgrammingLab/prolab-accounts/infra/store/feed" + githubstore "github.com/ProgrammingLab/prolab-accounts/infra/store/github" heartbeatstore "github.com/ProgrammingLab/prolab-accounts/infra/store/heartbeat" invitationstore "github.com/ProgrammingLab/prolab-accounts/infra/store/invitation" profilestore "github.com/ProgrammingLab/prolab-accounts/infra/store/profile" @@ -21,6 +22,7 @@ import ( sessionstore "github.com/ProgrammingLab/prolab-accounts/infra/store/session" userstore "github.com/ProgrammingLab/prolab-accounts/infra/store/user" userblogstore "github.com/ProgrammingLab/prolab-accounts/infra/store/user_blog" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) // StoreComponent is an interface of stores @@ -35,6 +37,7 @@ type StoreComponent interface { HeartbeatStore(ctx context.Context) store.HeartbeatStore DepartmentStore(ctx context.Context) store.DepartmentStore InvitationStore(ctx context.Context) store.InvitationStore + GitHubStore(ctx context.Context) store.GitHubStore } // NewStoreComponent returns new store component @@ -55,7 +58,7 @@ func NewStoreComponent(cfg *config.Config) (StoreComponent, error) { } return &storeComponentImpl{ - db: db, + db: sqlutil.New(db), client: cli, minioCli: min, cfg: cfg, @@ -130,7 +133,7 @@ func connectMinio(cfg *config.Config) (*minio.Client, error) { } type storeComponentImpl struct { - db *sql.DB + db *sqlutil.DB client *redis.Client minioCli *minio.Client cfg *config.Config @@ -175,3 +178,7 @@ func (s *storeComponentImpl) HeartbeatStore(ctx context.Context) store.Heartbeat func (s *storeComponentImpl) InvitationStore(ctx context.Context) store.InvitationStore { return invitationstore.NewInvitationStore(ctx, s.db) } + +func (s *storeComponentImpl) GitHubStore(ctx context.Context) store.GitHubStore { + return githubstore.NewGitHubStore(ctx, s.db, s.client) +} diff --git a/app/di/test_store_component.go b/app/di/test_store_component.go index 3547514c..b834bc22 100644 --- a/app/di/test_store_component.go +++ b/app/di/test_store_component.go @@ -10,6 +10,7 @@ import ( "github.com/ProgrammingLab/prolab-accounts/app/config" "github.com/ProgrammingLab/prolab-accounts/infra/record" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) // MustCreateTestStoreComponent creates test store component or exits @@ -20,7 +21,7 @@ func MustCreateTestStoreComponent(cfg *config.Config) *TestStoreComponent { return &TestStoreComponent{ storeComponentImpl: &storeComponentImpl{ - db: db, + db: sqlutil.New(db), cfg: cfg, }, } diff --git a/app/job/feed_job.go b/app/job/feed_job.go index d737949b..2b636862 100644 --- a/app/job/feed_job.go +++ b/app/job/feed_job.go @@ -6,10 +6,11 @@ import ( "google.golang.org/grpc/grpclog" + "github.com/ProgrammingLab/prolab-accounts/app/config" "github.com/ProgrammingLab/prolab-accounts/app/di" ) -func feedJob(ctx context.Context, store di.StoreComponent, debug bool) error { +func feedJob(ctx context.Context, store di.StoreComponent, cfg *config.Config) error { bs := store.UserBlogStore(ctx) blogs, err := bs.ListUserBlogs() if err != nil { @@ -32,11 +33,11 @@ func feedJob(ctx context.Context, store di.StoreComponent, debug bool) error { if err != nil { return err } - if debug { + if cfg.DebugLog { grpclog.Infof("feed job: created %v entries", n) } - <-time.After(100 * time.Millisecond) + time.Sleep(500 * time.Millisecond) } return nil diff --git a/app/job/github_job.go b/app/job/github_job.go new file mode 100644 index 00000000..8b726767 --- /dev/null +++ b/app/job/github_job.go @@ -0,0 +1,119 @@ +package job + +import ( + "context" + "time" + + "github.com/pkg/errors" + "github.com/shurcooL/githubv4" + "golang.org/x/oauth2" + + "github.com/ProgrammingLab/prolab-accounts/app/config" + "github.com/ProgrammingLab/prolab-accounts/app/di" + "github.com/ProgrammingLab/prolab-accounts/infra/record" + "github.com/ProgrammingLab/prolab-accounts/model" +) + +type githubUser struct { + User struct { + ContributionsCollection struct { + ContributionCalendar struct { + TotalContributions githubv4.Int + Weeks []struct { + ContributionDays []struct { + ContributionCount githubv4.Int + Date githubv4.String + } + } + } + } `graphql:"contributionsCollection(from:$from, to:$to)"` + } `graphql:"user(login:$login)"` +} + +const ( + contributionsFromDay = 60 + githubDateFormat = "2006-1-2" +) + +func githubJob(ctx context.Context, store di.StoreComponent, cfg *config.Config) error { + src := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: cfg.GitHubAccessToken}, + ) + httpClient := oauth2.NewClient(ctx, src) + cli := githubv4.NewClient(httpClient) + + us := store.UserStore(ctx) + next := model.UserID(1) + to := time.Now().UTC() + from := to.AddDate(0, 0, -contributionsFromDay).Round(time.Hour * 24) + for next != 0 { + users, nxt, err := us.ListPublicUsers(next, 100) + next = nxt + if err != nil { + return err + } + + for _, u := range users { + name := u.R.Profile.GithubUserName + if name.IsZero() { + continue + } + gu, err := getGitHubUser(ctx, cli, name.String, from, to) + if err != nil { + return err + } + + err = storeGitHubContributions(ctx, store, u, gu) + if err != nil { + return err + } + + time.Sleep(500 * time.Millisecond) + } + } + + return nil +} + +func getGitHubUser(ctx context.Context, cli *githubv4.Client, name string, from time.Time, to time.Time) (*githubUser, error) { + v := map[string]interface{}{ + "login": githubv4.String(name), + "from": githubv4.DateTime{Time: from}, + "to": githubv4.DateTime{Time: to}, + } + q := &githubUser{} + err := cli.Query(ctx, q, v) + if err != nil { + return nil, errors.WithStack(err) + } + + return q, nil +} + +func storeGitHubContributions(ctx context.Context, store di.StoreComponent, user *record.User, github *githubUser) error { + days := make([]*record.GithubContributionDay, 0, contributionsFromDay) + weeks := github.User.ContributionsCollection.ContributionCalendar.Weeks + for _, w := range weeks { + for _, d := range w.ContributionDays { + date, err := time.Parse(githubDateFormat, string(d.Date)) + if err != nil { + return errors.WithStack(err) + } + gd := &record.GithubContributionDay{ + Count: int(d.ContributionCount), + Date: date, + UserID: user.ID, + } + days = append(days, gd) + } + } + + c := &model.GitHubContributionCollection{ + UserID: model.UserID(user.ID), + TotalCount: int(github.User.ContributionsCollection.ContributionCalendar.TotalContributions), + Days: days, + } + gs := store.GitHubStore(ctx) + _, err := gs.UpdateContributionDays(c) + return err +} diff --git a/app/job/heartbeat_job.go b/app/job/heartbeat_job.go index 8cac0655..140ff565 100644 --- a/app/job/heartbeat_job.go +++ b/app/job/heartbeat_job.go @@ -5,10 +5,11 @@ import ( "github.com/pkg/errors" + "github.com/ProgrammingLab/prolab-accounts/app/config" "github.com/ProgrammingLab/prolab-accounts/app/di" ) -func heartbeatJob(ctx context.Context, store di.StoreComponent, debug bool) error { +func heartbeatJob(ctx context.Context, store di.StoreComponent, cfg *config.Config) error { s := store.HeartbeatStore(ctx) return errors.WithStack(s.Beat()) } diff --git a/app/job/job.go b/app/job/job.go index 061a1b94..08402536 100644 --- a/app/job/job.go +++ b/app/job/job.go @@ -16,12 +16,13 @@ var ( stop = make(chan struct{}) jobs = []Job{ feedJob, + githubJob, heartbeatJob, } ) // Job represents job for worker -type Job func(ctx context.Context, store di.StoreComponent, debug bool) error +type Job func(ctx context.Context, store di.StoreComponent, cfg *config.Config) error // Start starts the worker func Start(store di.StoreComponent, cfg *config.Config) { @@ -58,7 +59,7 @@ func run(store di.StoreComponent, cfg *config.Config) { select { case <-time.After(interval): for _, j := range jobs { - err := j(context.Background(), store, cfg.DebugLog) + err := j(context.Background(), store, cfg) if err != nil { grpclog.Errorf("job error: %+v", err) } diff --git a/app/run.go b/app/run.go index a1634031..2725e6e4 100644 --- a/app/run.go +++ b/app/run.go @@ -61,6 +61,7 @@ func Run() error { server.NewRoleServiceServer(store), server.NewDepartmentServiceServer(store), server.NewInvitationServiceServer(store, cli), + server.NewContributionConllectionServiceServer(store, cfg), ), ) diff --git a/app/server/contribution_conllections_server.go b/app/server/contribution_conllections_server.go new file mode 100644 index 00000000..fd096e32 --- /dev/null +++ b/app/server/contribution_conllections_server.go @@ -0,0 +1,86 @@ +package server + +import ( + "context" + + "github.com/izumin5210/grapi/pkg/grapiserver" + "google.golang.org/grpc/grpclog" + + api_pb "github.com/ProgrammingLab/prolab-accounts/api" + "github.com/ProgrammingLab/prolab-accounts/app/config" + "github.com/ProgrammingLab/prolab-accounts/app/di" + "github.com/ProgrammingLab/prolab-accounts/infra/record" + "github.com/ProgrammingLab/prolab-accounts/model" +) + +// ContributionConllectionServiceServer is a composite interface of api_pb.ContributionConllectionServiceServer and grapiserver.Server. +type ContributionConllectionServiceServer interface { + api_pb.ContributionConllectionServiceServer + grapiserver.Server +} + +// NewContributionConllectionServiceServer creates a new ContributionConllectionServiceServer instance. +func NewContributionConllectionServiceServer(store di.StoreComponent, cfg *config.Config) ContributionConllectionServiceServer { + return &contributionConllectionServiceServerImpl{ + StoreComponent: store, + cfg: cfg, + } +} + +type contributionConllectionServiceServerImpl struct { + di.StoreComponent + cfg *config.Config +} + +func (s *contributionConllectionServiceServerImpl) ListContributionConllections(ctx context.Context, req *api_pb.ListContributionConllectionsRequest) (*api_pb.ListContributionConllectionsResponse, error) { + gs := s.GitHubStore(ctx) + grpclog.Info(req.GetUsersCount()) + cols, err := gs.ListContributionCollections(int(req.GetUsersCount())) + if err != nil { + return nil, err + } + + resp := contributionConllectionsToResponse(cols, s.cfg) + return &api_pb.ListContributionConllectionsResponse{ + ContributionConllections: resp, + }, nil +} + +func contributionConllectionsToResponse(cols []*model.GitHubContributionCollection, cfg *config.Config) []*api_pb.ContributionConllection { + resp := make([]*api_pb.ContributionConllection, 0, len(cols)) + for _, c := range cols { + rc := contributionConllectionToResponse(c, cfg) + if rc == nil { + continue + } + resp = append(resp, rc) + } + + return resp +} + +func contributionConllectionToResponse(col *model.GitHubContributionCollection, cfg *config.Config) *api_pb.ContributionConllection { + if len(col.Days) == 0 { + return nil + } + + u := col.Days[0].R.User + return &api_pb.ContributionConllection{ + User: userToResponse(u, false, cfg), + TotalCount: int32(col.TotalCount), + Days: contributionDaysToResponse(col.Days), + } +} + +func contributionDaysToResponse(days []*record.GithubContributionDay) []*api_pb.ContributionDay { + resp := make([]*api_pb.ContributionDay, 0, len(days)) + for _, d := range days { + rd := &api_pb.ContributionDay{ + Date: timeToResponse(d.Date), + Count: int32(d.Count), + } + resp = append(resp, rd) + } + + return resp +} diff --git a/app/server/contribution_conllections_server_register_funcs.go b/app/server/contribution_conllections_server_register_funcs.go new file mode 100644 index 00000000..4bb3a892 --- /dev/null +++ b/app/server/contribution_conllections_server_register_funcs.go @@ -0,0 +1,22 @@ +// Code generated by github.com/izumin5210/grapi. DO NOT EDIT. + +package server + +import ( + "context" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc" + + api_pb "github.com/ProgrammingLab/prolab-accounts/api" +) + +// RegisterWithServer implements grapiserver.Server.RegisterWithServer. +func (s *contributionConllectionServiceServerImpl) RegisterWithServer(grpcSvr *grpc.Server) { + api_pb.RegisterContributionConllectionServiceServer(grpcSvr, s) +} + +// RegisterWithHandler implements grapiserver.Server.RegisterWithHandler. +func (s *contributionConllectionServiceServerImpl) RegisterWithHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return api_pb.RegisterContributionConllectionServiceHandler(ctx, mux, conn) +} diff --git a/app/server/contribution_conllections_server_test.go b/app/server/contribution_conllections_server_test.go new file mode 100644 index 00000000..abb4e431 --- /dev/null +++ b/app/server/contribution_conllections_server_test.go @@ -0,0 +1 @@ +package server diff --git a/db/Schemafile b/db/Schemafile index 83af7732..5158d865 100644 --- a/db/Schemafile +++ b/db/Schemafile @@ -5,3 +5,4 @@ require 'departments.schema' require 'blogs.schema' require 'entries.schema' require 'invitations.schema' +require 'github_contribution_days.schema' diff --git a/db/github_contribution_days.schema b/db/github_contribution_days.schema new file mode 100644 index 00000000..824bb25f --- /dev/null +++ b/db/github_contribution_days.schema @@ -0,0 +1,8 @@ +create_table :github_contribution_days, force: :cascade do |t| + t.integer :count, null: false + t.date :date, null: false + t.references :user, foreign_key: true, null: false + t.timestamps +end + +add_foreign_key :github_contribution_days, :users diff --git a/gen.go b/gen.go index 9775269d..63a1d5ea 100644 --- a/gen.go +++ b/gen.go @@ -2,4 +2,4 @@ package accounts //go:generate gex --build //go:generate gex sqlboiler psql -//go:generate packr2 +//go:generate gex packr2 diff --git a/infra/record/boil_suites_test.go b/infra/record/boil_suites_test.go index 37613235..8c1ad44d 100644 --- a/infra/record/boil_suites_test.go +++ b/infra/record/boil_suites_test.go @@ -15,6 +15,7 @@ func TestParent(t *testing.T) { t.Run("Blogs", testBlogs) t.Run("Departments", testDepartments) t.Run("Entries", testEntries) + t.Run("GithubContributionDays", testGithubContributionDays) t.Run("Invitations", testInvitations) t.Run("Profiles", testProfiles) t.Run("Roles", testRoles) @@ -25,6 +26,7 @@ func TestDelete(t *testing.T) { t.Run("Blogs", testBlogsDelete) t.Run("Departments", testDepartmentsDelete) t.Run("Entries", testEntriesDelete) + t.Run("GithubContributionDays", testGithubContributionDaysDelete) t.Run("Invitations", testInvitationsDelete) t.Run("Profiles", testProfilesDelete) t.Run("Roles", testRolesDelete) @@ -35,6 +37,7 @@ func TestQueryDeleteAll(t *testing.T) { t.Run("Blogs", testBlogsQueryDeleteAll) t.Run("Departments", testDepartmentsQueryDeleteAll) t.Run("Entries", testEntriesQueryDeleteAll) + t.Run("GithubContributionDays", testGithubContributionDaysQueryDeleteAll) t.Run("Invitations", testInvitationsQueryDeleteAll) t.Run("Profiles", testProfilesQueryDeleteAll) t.Run("Roles", testRolesQueryDeleteAll) @@ -45,6 +48,7 @@ func TestSliceDeleteAll(t *testing.T) { t.Run("Blogs", testBlogsSliceDeleteAll) t.Run("Departments", testDepartmentsSliceDeleteAll) t.Run("Entries", testEntriesSliceDeleteAll) + t.Run("GithubContributionDays", testGithubContributionDaysSliceDeleteAll) t.Run("Invitations", testInvitationsSliceDeleteAll) t.Run("Profiles", testProfilesSliceDeleteAll) t.Run("Roles", testRolesSliceDeleteAll) @@ -55,6 +59,7 @@ func TestExists(t *testing.T) { t.Run("Blogs", testBlogsExists) t.Run("Departments", testDepartmentsExists) t.Run("Entries", testEntriesExists) + t.Run("GithubContributionDays", testGithubContributionDaysExists) t.Run("Invitations", testInvitationsExists) t.Run("Profiles", testProfilesExists) t.Run("Roles", testRolesExists) @@ -65,6 +70,7 @@ func TestFind(t *testing.T) { t.Run("Blogs", testBlogsFind) t.Run("Departments", testDepartmentsFind) t.Run("Entries", testEntriesFind) + t.Run("GithubContributionDays", testGithubContributionDaysFind) t.Run("Invitations", testInvitationsFind) t.Run("Profiles", testProfilesFind) t.Run("Roles", testRolesFind) @@ -75,6 +81,7 @@ func TestBind(t *testing.T) { t.Run("Blogs", testBlogsBind) t.Run("Departments", testDepartmentsBind) t.Run("Entries", testEntriesBind) + t.Run("GithubContributionDays", testGithubContributionDaysBind) t.Run("Invitations", testInvitationsBind) t.Run("Profiles", testProfilesBind) t.Run("Roles", testRolesBind) @@ -85,6 +92,7 @@ func TestOne(t *testing.T) { t.Run("Blogs", testBlogsOne) t.Run("Departments", testDepartmentsOne) t.Run("Entries", testEntriesOne) + t.Run("GithubContributionDays", testGithubContributionDaysOne) t.Run("Invitations", testInvitationsOne) t.Run("Profiles", testProfilesOne) t.Run("Roles", testRolesOne) @@ -95,6 +103,7 @@ func TestAll(t *testing.T) { t.Run("Blogs", testBlogsAll) t.Run("Departments", testDepartmentsAll) t.Run("Entries", testEntriesAll) + t.Run("GithubContributionDays", testGithubContributionDaysAll) t.Run("Invitations", testInvitationsAll) t.Run("Profiles", testProfilesAll) t.Run("Roles", testRolesAll) @@ -105,6 +114,7 @@ func TestCount(t *testing.T) { t.Run("Blogs", testBlogsCount) t.Run("Departments", testDepartmentsCount) t.Run("Entries", testEntriesCount) + t.Run("GithubContributionDays", testGithubContributionDaysCount) t.Run("Invitations", testInvitationsCount) t.Run("Profiles", testProfilesCount) t.Run("Roles", testRolesCount) @@ -115,6 +125,7 @@ func TestHooks(t *testing.T) { t.Run("Blogs", testBlogsHooks) t.Run("Departments", testDepartmentsHooks) t.Run("Entries", testEntriesHooks) + t.Run("GithubContributionDays", testGithubContributionDaysHooks) t.Run("Invitations", testInvitationsHooks) t.Run("Profiles", testProfilesHooks) t.Run("Roles", testRolesHooks) @@ -128,6 +139,8 @@ func TestInsert(t *testing.T) { t.Run("Departments", testDepartmentsInsertWhitelist) t.Run("Entries", testEntriesInsert) t.Run("Entries", testEntriesInsertWhitelist) + t.Run("GithubContributionDays", testGithubContributionDaysInsert) + t.Run("GithubContributionDays", testGithubContributionDaysInsertWhitelist) t.Run("Invitations", testInvitationsInsert) t.Run("Invitations", testInvitationsInsertWhitelist) t.Run("Profiles", testProfilesInsert) @@ -144,6 +157,7 @@ func TestToOne(t *testing.T) { t.Run("BlogToUserUsingUser", testBlogToOneUserUsingUser) t.Run("EntryToUserUsingAuthor", testEntryToOneUserUsingAuthor) t.Run("EntryToBlogUsingBlog", testEntryToOneBlogUsingBlog) + t.Run("GithubContributionDayToUserUsingUser", testGithubContributionDayToOneUserUsingUser) t.Run("InvitationToUserUsingInviter", testInvitationToOneUserUsingInviter) t.Run("ProfileToDepartmentUsingDepartment", testProfileToOneDepartmentUsingDepartment) t.Run("ProfileToRoleUsingRole", testProfileToOneRoleUsingRole) @@ -163,6 +177,7 @@ func TestToMany(t *testing.T) { t.Run("RoleToProfiles", testRoleToManyProfiles) t.Run("UserToBlogs", testUserToManyBlogs) t.Run("UserToAuthorEntries", testUserToManyAuthorEntries) + t.Run("UserToGithubContributionDays", testUserToManyGithubContributionDays) t.Run("UserToInviterInvitations", testUserToManyInviterInvitations) } @@ -172,6 +187,7 @@ func TestToOneSet(t *testing.T) { t.Run("BlogToUserUsingBlogs", testBlogToOneSetOpUserUsingUser) t.Run("EntryToUserUsingAuthorEntries", testEntryToOneSetOpUserUsingAuthor) t.Run("EntryToBlogUsingEntries", testEntryToOneSetOpBlogUsingBlog) + t.Run("GithubContributionDayToUserUsingGithubContributionDays", testGithubContributionDayToOneSetOpUserUsingUser) t.Run("InvitationToUserUsingInviterInvitations", testInvitationToOneSetOpUserUsingInviter) t.Run("ProfileToDepartmentUsingProfiles", testProfileToOneSetOpDepartmentUsingDepartment) t.Run("ProfileToRoleUsingProfiles", testProfileToOneSetOpRoleUsingRole) @@ -203,6 +219,7 @@ func TestToManyAdd(t *testing.T) { t.Run("RoleToProfiles", testRoleToManyAddOpProfiles) t.Run("UserToBlogs", testUserToManyAddOpBlogs) t.Run("UserToAuthorEntries", testUserToManyAddOpAuthorEntries) + t.Run("UserToGithubContributionDays", testUserToManyAddOpGithubContributionDays) t.Run("UserToInviterInvitations", testUserToManyAddOpInviterInvitations) } @@ -226,6 +243,7 @@ func TestReload(t *testing.T) { t.Run("Blogs", testBlogsReload) t.Run("Departments", testDepartmentsReload) t.Run("Entries", testEntriesReload) + t.Run("GithubContributionDays", testGithubContributionDaysReload) t.Run("Invitations", testInvitationsReload) t.Run("Profiles", testProfilesReload) t.Run("Roles", testRolesReload) @@ -236,6 +254,7 @@ func TestReloadAll(t *testing.T) { t.Run("Blogs", testBlogsReloadAll) t.Run("Departments", testDepartmentsReloadAll) t.Run("Entries", testEntriesReloadAll) + t.Run("GithubContributionDays", testGithubContributionDaysReloadAll) t.Run("Invitations", testInvitationsReloadAll) t.Run("Profiles", testProfilesReloadAll) t.Run("Roles", testRolesReloadAll) @@ -246,6 +265,7 @@ func TestSelect(t *testing.T) { t.Run("Blogs", testBlogsSelect) t.Run("Departments", testDepartmentsSelect) t.Run("Entries", testEntriesSelect) + t.Run("GithubContributionDays", testGithubContributionDaysSelect) t.Run("Invitations", testInvitationsSelect) t.Run("Profiles", testProfilesSelect) t.Run("Roles", testRolesSelect) @@ -256,6 +276,7 @@ func TestUpdate(t *testing.T) { t.Run("Blogs", testBlogsUpdate) t.Run("Departments", testDepartmentsUpdate) t.Run("Entries", testEntriesUpdate) + t.Run("GithubContributionDays", testGithubContributionDaysUpdate) t.Run("Invitations", testInvitationsUpdate) t.Run("Profiles", testProfilesUpdate) t.Run("Roles", testRolesUpdate) @@ -266,6 +287,7 @@ func TestSliceUpdateAll(t *testing.T) { t.Run("Blogs", testBlogsSliceUpdateAll) t.Run("Departments", testDepartmentsSliceUpdateAll) t.Run("Entries", testEntriesSliceUpdateAll) + t.Run("GithubContributionDays", testGithubContributionDaysSliceUpdateAll) t.Run("Invitations", testInvitationsSliceUpdateAll) t.Run("Profiles", testProfilesSliceUpdateAll) t.Run("Roles", testRolesSliceUpdateAll) diff --git a/infra/record/boil_table_names.go b/infra/record/boil_table_names.go index b7160214..0ab65842 100644 --- a/infra/record/boil_table_names.go +++ b/infra/record/boil_table_names.go @@ -4,19 +4,21 @@ package record var TableNames = struct { - Blogs string - Departments string - Entries string - Invitations string - Profiles string - Roles string - Users string + Blogs string + Departments string + Entries string + GithubContributionDays string + Invitations string + Profiles string + Roles string + Users string }{ - Blogs: "blogs", - Departments: "departments", - Entries: "entries", - Invitations: "invitations", - Profiles: "profiles", - Roles: "roles", - Users: "users", + Blogs: "blogs", + Departments: "departments", + Entries: "entries", + GithubContributionDays: "github_contribution_days", + Invitations: "invitations", + Profiles: "profiles", + Roles: "roles", + Users: "users", } diff --git a/infra/record/github_contribution_days.go b/infra/record/github_contribution_days.go new file mode 100644 index 00000000..99caf015 --- /dev/null +++ b/infra/record/github_contribution_days.go @@ -0,0 +1,1090 @@ +// Code generated by SQLBoiler (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package record + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/pkg/errors" + "github.com/volatiletech/sqlboiler/boil" + "github.com/volatiletech/sqlboiler/queries" + "github.com/volatiletech/sqlboiler/queries/qm" + "github.com/volatiletech/sqlboiler/queries/qmhelper" + "github.com/volatiletech/sqlboiler/strmangle" +) + +// GithubContributionDay is an object representing the database table. +type GithubContributionDay struct { + ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"` + Count int `boil:"count" json:"count" toml:"count" yaml:"count"` + Date time.Time `boil:"date" json:"date" toml:"date" yaml:"date"` + UserID int64 `boil:"user_id" json:"user_id" toml:"user_id" yaml:"user_id"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + + R *githubContributionDayR `boil:"-" json:"-" toml:"-" yaml:"-"` + L githubContributionDayL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var GithubContributionDayColumns = struct { + ID string + Count string + Date string + UserID string + CreatedAt string + UpdatedAt string +}{ + ID: "id", + Count: "count", + Date: "date", + UserID: "user_id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// Generated where + +type whereHelperint struct{ field string } + +func (w whereHelperint) EQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint) NEQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint) LT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint) LTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint) GT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint) GTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } + +var GithubContributionDayWhere = struct { + ID whereHelperint64 + Count whereHelperint + Date whereHelpertime_Time + UserID whereHelperint64 + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time +}{ + ID: whereHelperint64{field: `id`}, + Count: whereHelperint{field: `count`}, + Date: whereHelpertime_Time{field: `date`}, + UserID: whereHelperint64{field: `user_id`}, + CreatedAt: whereHelpertime_Time{field: `created_at`}, + UpdatedAt: whereHelpertime_Time{field: `updated_at`}, +} + +// GithubContributionDayRels is where relationship names are stored. +var GithubContributionDayRels = struct { + User string +}{ + User: "User", +} + +// githubContributionDayR is where relationships are stored. +type githubContributionDayR struct { + User *User +} + +// NewStruct creates a new relationship struct +func (*githubContributionDayR) NewStruct() *githubContributionDayR { + return &githubContributionDayR{} +} + +// githubContributionDayL is where Load methods for each relationship are stored. +type githubContributionDayL struct{} + +var ( + githubContributionDayColumns = []string{"id", "count", "date", "user_id", "created_at", "updated_at"} + githubContributionDayColumnsWithoutDefault = []string{"count", "date", "user_id", "created_at", "updated_at"} + githubContributionDayColumnsWithDefault = []string{"id"} + githubContributionDayPrimaryKeyColumns = []string{"id"} +) + +type ( + // GithubContributionDaySlice is an alias for a slice of pointers to GithubContributionDay. + // This should generally be used opposed to []GithubContributionDay. + GithubContributionDaySlice []*GithubContributionDay + // GithubContributionDayHook is the signature for custom GithubContributionDay hook methods + GithubContributionDayHook func(context.Context, boil.ContextExecutor, *GithubContributionDay) error + + githubContributionDayQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + githubContributionDayType = reflect.TypeOf(&GithubContributionDay{}) + githubContributionDayMapping = queries.MakeStructMapping(githubContributionDayType) + githubContributionDayPrimaryKeyMapping, _ = queries.BindMapping(githubContributionDayType, githubContributionDayMapping, githubContributionDayPrimaryKeyColumns) + githubContributionDayInsertCacheMut sync.RWMutex + githubContributionDayInsertCache = make(map[string]insertCache) + githubContributionDayUpdateCacheMut sync.RWMutex + githubContributionDayUpdateCache = make(map[string]updateCache) + githubContributionDayUpsertCacheMut sync.RWMutex + githubContributionDayUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +var githubContributionDayBeforeInsertHooks []GithubContributionDayHook +var githubContributionDayBeforeUpdateHooks []GithubContributionDayHook +var githubContributionDayBeforeDeleteHooks []GithubContributionDayHook +var githubContributionDayBeforeUpsertHooks []GithubContributionDayHook + +var githubContributionDayAfterInsertHooks []GithubContributionDayHook +var githubContributionDayAfterSelectHooks []GithubContributionDayHook +var githubContributionDayAfterUpdateHooks []GithubContributionDayHook +var githubContributionDayAfterDeleteHooks []GithubContributionDayHook +var githubContributionDayAfterUpsertHooks []GithubContributionDayHook + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *GithubContributionDay) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range githubContributionDayBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *GithubContributionDay) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range githubContributionDayBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *GithubContributionDay) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range githubContributionDayBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *GithubContributionDay) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range githubContributionDayBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *GithubContributionDay) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range githubContributionDayAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *GithubContributionDay) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range githubContributionDayAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *GithubContributionDay) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range githubContributionDayAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *GithubContributionDay) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range githubContributionDayAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *GithubContributionDay) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range githubContributionDayAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddGithubContributionDayHook registers your hook function for all future operations. +func AddGithubContributionDayHook(hookPoint boil.HookPoint, githubContributionDayHook GithubContributionDayHook) { + switch hookPoint { + case boil.BeforeInsertHook: + githubContributionDayBeforeInsertHooks = append(githubContributionDayBeforeInsertHooks, githubContributionDayHook) + case boil.BeforeUpdateHook: + githubContributionDayBeforeUpdateHooks = append(githubContributionDayBeforeUpdateHooks, githubContributionDayHook) + case boil.BeforeDeleteHook: + githubContributionDayBeforeDeleteHooks = append(githubContributionDayBeforeDeleteHooks, githubContributionDayHook) + case boil.BeforeUpsertHook: + githubContributionDayBeforeUpsertHooks = append(githubContributionDayBeforeUpsertHooks, githubContributionDayHook) + case boil.AfterInsertHook: + githubContributionDayAfterInsertHooks = append(githubContributionDayAfterInsertHooks, githubContributionDayHook) + case boil.AfterSelectHook: + githubContributionDayAfterSelectHooks = append(githubContributionDayAfterSelectHooks, githubContributionDayHook) + case boil.AfterUpdateHook: + githubContributionDayAfterUpdateHooks = append(githubContributionDayAfterUpdateHooks, githubContributionDayHook) + case boil.AfterDeleteHook: + githubContributionDayAfterDeleteHooks = append(githubContributionDayAfterDeleteHooks, githubContributionDayHook) + case boil.AfterUpsertHook: + githubContributionDayAfterUpsertHooks = append(githubContributionDayAfterUpsertHooks, githubContributionDayHook) + } +} + +// One returns a single githubContributionDay record from the query. +func (q githubContributionDayQuery) One(ctx context.Context, exec boil.ContextExecutor) (*GithubContributionDay, error) { + o := &GithubContributionDay{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "record: failed to execute a one query for github_contribution_days") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all GithubContributionDay records from the query. +func (q githubContributionDayQuery) All(ctx context.Context, exec boil.ContextExecutor) (GithubContributionDaySlice, error) { + var o []*GithubContributionDay + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "record: failed to assign all query results to GithubContributionDay slice") + } + + if len(githubContributionDayAfterSelectHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + } + } + + return o, nil +} + +// Count returns the count of all GithubContributionDay records in the query. +func (q githubContributionDayQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "record: failed to count github_contribution_days rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q githubContributionDayQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "record: failed to check if github_contribution_days exists") + } + + return count > 0, nil +} + +// User pointed to by the foreign key. +func (o *GithubContributionDay) User(mods ...qm.QueryMod) userQuery { + queryMods := []qm.QueryMod{ + qm.Where("id=?", o.UserID), + } + + queryMods = append(queryMods, mods...) + + query := Users(queryMods...) + queries.SetFrom(query.Query, "\"users\"") + + return query +} + +// LoadUser allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for an N-1 relationship. +func (githubContributionDayL) LoadUser(ctx context.Context, e boil.ContextExecutor, singular bool, maybeGithubContributionDay interface{}, mods queries.Applicator) error { + var slice []*GithubContributionDay + var object *GithubContributionDay + + if singular { + object = maybeGithubContributionDay.(*GithubContributionDay) + } else { + slice = *maybeGithubContributionDay.(*[]*GithubContributionDay) + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = &githubContributionDayR{} + } + args = append(args, object.UserID) + + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = &githubContributionDayR{} + } + + for _, a := range args { + if a == obj.UserID { + continue Outer + } + } + + args = append(args, obj.UserID) + + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery(qm.From(`users`), qm.WhereIn(`id in ?`, args...)) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load User") + } + + var resultSlice []*User + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice User") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results of eager load for users") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for users") + } + + if len(githubContributionDayAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + + if len(resultSlice) == 0 { + return nil + } + + if singular { + foreign := resultSlice[0] + object.R.User = foreign + if foreign.R == nil { + foreign.R = &userR{} + } + foreign.R.GithubContributionDays = append(foreign.R.GithubContributionDays, object) + return nil + } + + for _, local := range slice { + for _, foreign := range resultSlice { + if local.UserID == foreign.ID { + local.R.User = foreign + if foreign.R == nil { + foreign.R = &userR{} + } + foreign.R.GithubContributionDays = append(foreign.R.GithubContributionDays, local) + break + } + } + } + + return nil +} + +// SetUser of the githubContributionDay to the related item. +// Sets o.R.User to related. +// Adds o to related.R.GithubContributionDays. +func (o *GithubContributionDay) SetUser(ctx context.Context, exec boil.ContextExecutor, insert bool, related *User) error { + var err error + if insert { + if err = related.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + + updateQuery := fmt.Sprintf( + "UPDATE \"github_contribution_days\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"user_id"}), + strmangle.WhereClause("\"", "\"", 2, githubContributionDayPrimaryKeyColumns), + ) + values := []interface{}{related.ID, o.ID} + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, updateQuery) + fmt.Fprintln(boil.DebugWriter, values) + } + + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update local table") + } + + o.UserID = related.ID + if o.R == nil { + o.R = &githubContributionDayR{ + User: related, + } + } else { + o.R.User = related + } + + if related.R == nil { + related.R = &userR{ + GithubContributionDays: GithubContributionDaySlice{o}, + } + } else { + related.R.GithubContributionDays = append(related.R.GithubContributionDays, o) + } + + return nil +} + +// GithubContributionDays retrieves all the records using an executor. +func GithubContributionDays(mods ...qm.QueryMod) githubContributionDayQuery { + mods = append(mods, qm.From("\"github_contribution_days\"")) + return githubContributionDayQuery{NewQuery(mods...)} +} + +// FindGithubContributionDay retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindGithubContributionDay(ctx context.Context, exec boil.ContextExecutor, iD int64, selectCols ...string) (*GithubContributionDay, error) { + githubContributionDayObj := &GithubContributionDay{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"github_contribution_days\" where \"id\"=$1", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, githubContributionDayObj) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "record: unable to select from github_contribution_days") + } + + return githubContributionDayObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *GithubContributionDay) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("record: no github_contribution_days provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(githubContributionDayColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + githubContributionDayInsertCacheMut.RLock() + cache, cached := githubContributionDayInsertCache[key] + githubContributionDayInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + githubContributionDayColumns, + githubContributionDayColumnsWithDefault, + githubContributionDayColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(githubContributionDayType, githubContributionDayMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(githubContributionDayType, githubContributionDayMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"github_contribution_days\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"github_contribution_days\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, cache.query) + fmt.Fprintln(boil.DebugWriter, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "record: unable to insert into github_contribution_days") + } + + if !cached { + githubContributionDayInsertCacheMut.Lock() + githubContributionDayInsertCache[key] = cache + githubContributionDayInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the GithubContributionDay. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *GithubContributionDay) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { + return 0, err + } + key := makeCacheKey(columns, nil) + githubContributionDayUpdateCacheMut.RLock() + cache, cached := githubContributionDayUpdateCache[key] + githubContributionDayUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + githubContributionDayColumns, + githubContributionDayPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("record: unable to update github_contribution_days, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"github_contribution_days\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, githubContributionDayPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(githubContributionDayType, githubContributionDayMapping, append(wl, githubContributionDayPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, cache.query) + fmt.Fprintln(boil.DebugWriter, values) + } + + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "record: unable to update github_contribution_days row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "record: failed to get rows affected by update for github_contribution_days") + } + + if !cached { + githubContributionDayUpdateCacheMut.Lock() + githubContributionDayUpdateCache[key] = cache + githubContributionDayUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q githubContributionDayQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "record: unable to update all for github_contribution_days") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "record: unable to retrieve rows affected for github_contribution_days") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o GithubContributionDaySlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("record: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), githubContributionDayPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"github_contribution_days\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, githubContributionDayPrimaryKeyColumns, len(o))) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, args...) + } + + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "record: unable to update all in githubContributionDay slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "record: unable to retrieve rows affected all in update all githubContributionDay") + } + return rowsAff, nil +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *GithubContributionDay) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns) error { + if o == nil { + return errors.New("record: no github_contribution_days provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + if err := o.doBeforeUpsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(githubContributionDayColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + githubContributionDayUpsertCacheMut.RLock() + cache, cached := githubContributionDayUpsertCache[key] + githubContributionDayUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, ret := insertColumns.InsertColumnSet( + githubContributionDayColumns, + githubContributionDayColumnsWithDefault, + githubContributionDayColumnsWithoutDefault, + nzDefaults, + ) + update := updateColumns.UpdateColumnSet( + githubContributionDayColumns, + githubContributionDayPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("record: unable to upsert github_contribution_days, could not build update column list") + } + + conflict := conflictColumns + if len(conflict) == 0 { + conflict = make([]string, len(githubContributionDayPrimaryKeyColumns)) + copy(conflict, githubContributionDayPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"github_contribution_days\"", updateOnConflict, ret, update, conflict, insert) + + cache.valueMapping, err = queries.BindMapping(githubContributionDayType, githubContributionDayMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(githubContributionDayType, githubContributionDayMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, cache.query) + fmt.Fprintln(boil.DebugWriter, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if err == sql.ErrNoRows { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "record: unable to upsert github_contribution_days") + } + + if !cached { + githubContributionDayUpsertCacheMut.Lock() + githubContributionDayUpsertCache[key] = cache + githubContributionDayUpsertCacheMut.Unlock() + } + + return o.doAfterUpsertHooks(ctx, exec) +} + +// Delete deletes a single GithubContributionDay record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *GithubContributionDay) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("record: no GithubContributionDay provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), githubContributionDayPrimaryKeyMapping) + sql := "DELETE FROM \"github_contribution_days\" WHERE \"id\"=$1" + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, args...) + } + + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "record: unable to delete from github_contribution_days") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "record: failed to get rows affected by delete for github_contribution_days") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q githubContributionDayQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("record: no githubContributionDayQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "record: unable to delete all from github_contribution_days") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "record: failed to get rows affected by deleteall for github_contribution_days") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o GithubContributionDaySlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("record: no GithubContributionDay slice provided for delete all") + } + + if len(o) == 0 { + return 0, nil + } + + if len(githubContributionDayBeforeDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), githubContributionDayPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"github_contribution_days\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, githubContributionDayPrimaryKeyColumns, len(o)) + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, args) + } + + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "record: unable to delete all from githubContributionDay slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "record: failed to get rows affected by deleteall for github_contribution_days") + } + + if len(githubContributionDayAfterDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + return rowsAff, nil +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *GithubContributionDay) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindGithubContributionDay(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *GithubContributionDaySlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := GithubContributionDaySlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), githubContributionDayPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"github_contribution_days\".* FROM \"github_contribution_days\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, githubContributionDayPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "record: unable to reload all in GithubContributionDaySlice") + } + + *o = slice + + return nil +} + +// GithubContributionDayExists checks if the GithubContributionDay row exists. +func GithubContributionDayExists(ctx context.Context, exec boil.ContextExecutor, iD int64) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"github_contribution_days\" where \"id\"=$1 limit 1)" + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, sql) + fmt.Fprintln(boil.DebugWriter, iD) + } + + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "record: unable to check if github_contribution_days exists") + } + + return exists, nil +} diff --git a/infra/record/github_contribution_days_test.go b/infra/record/github_contribution_days_test.go new file mode 100644 index 00000000..b9def8ff --- /dev/null +++ b/infra/record/github_contribution_days_test.go @@ -0,0 +1,841 @@ +// Code generated by SQLBoiler (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package record + +import ( + "bytes" + "context" + "reflect" + "testing" + + "github.com/volatiletech/sqlboiler/boil" + "github.com/volatiletech/sqlboiler/queries" + "github.com/volatiletech/sqlboiler/randomize" + "github.com/volatiletech/sqlboiler/strmangle" +) + +var ( + // Relationships sometimes use the reflection helper queries.Equal/queries.Assign + // so force a package dependency in case they don't. + _ = queries.Equal +) + +func testGithubContributionDays(t *testing.T) { + t.Parallel() + + query := GithubContributionDays() + + if query.Query == nil { + t.Error("expected a query, got nothing") + } +} + +func testGithubContributionDaysDelete(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if rowsAff, err := o.Delete(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testGithubContributionDaysQueryDeleteAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if rowsAff, err := GithubContributionDays().DeleteAll(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testGithubContributionDaysSliceDeleteAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice := GithubContributionDaySlice{o} + + if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testGithubContributionDaysExists(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + e, err := GithubContributionDayExists(ctx, tx, o.ID) + if err != nil { + t.Errorf("Unable to check if GithubContributionDay exists: %s", err) + } + if !e { + t.Errorf("Expected GithubContributionDayExists to return true, but got false.") + } +} + +func testGithubContributionDaysFind(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + githubContributionDayFound, err := FindGithubContributionDay(ctx, tx, o.ID) + if err != nil { + t.Error(err) + } + + if githubContributionDayFound == nil { + t.Error("want a record, got nil") + } +} + +func testGithubContributionDaysBind(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if err = GithubContributionDays().Bind(ctx, tx, o); err != nil { + t.Error(err) + } +} + +func testGithubContributionDaysOne(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if x, err := GithubContributionDays().One(ctx, tx); err != nil { + t.Error(err) + } else if x == nil { + t.Error("expected to get a non nil record") + } +} + +func testGithubContributionDaysAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + githubContributionDayOne := &GithubContributionDay{} + githubContributionDayTwo := &GithubContributionDay{} + if err = randomize.Struct(seed, githubContributionDayOne, githubContributionDayDBTypes, false, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + if err = randomize.Struct(seed, githubContributionDayTwo, githubContributionDayDBTypes, false, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = githubContributionDayOne.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + if err = githubContributionDayTwo.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice, err := GithubContributionDays().All(ctx, tx) + if err != nil { + t.Error(err) + } + + if len(slice) != 2 { + t.Error("want 2 records, got:", len(slice)) + } +} + +func testGithubContributionDaysCount(t *testing.T) { + t.Parallel() + + var err error + seed := randomize.NewSeed() + githubContributionDayOne := &GithubContributionDay{} + githubContributionDayTwo := &GithubContributionDay{} + if err = randomize.Struct(seed, githubContributionDayOne, githubContributionDayDBTypes, false, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + if err = randomize.Struct(seed, githubContributionDayTwo, githubContributionDayDBTypes, false, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = githubContributionDayOne.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + if err = githubContributionDayTwo.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 2 { + t.Error("want 2 records, got:", count) + } +} + +func githubContributionDayBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *GithubContributionDay) error { + *o = GithubContributionDay{} + return nil +} + +func githubContributionDayAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *GithubContributionDay) error { + *o = GithubContributionDay{} + return nil +} + +func githubContributionDayAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *GithubContributionDay) error { + *o = GithubContributionDay{} + return nil +} + +func githubContributionDayBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *GithubContributionDay) error { + *o = GithubContributionDay{} + return nil +} + +func githubContributionDayAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *GithubContributionDay) error { + *o = GithubContributionDay{} + return nil +} + +func githubContributionDayBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *GithubContributionDay) error { + *o = GithubContributionDay{} + return nil +} + +func githubContributionDayAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *GithubContributionDay) error { + *o = GithubContributionDay{} + return nil +} + +func githubContributionDayBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *GithubContributionDay) error { + *o = GithubContributionDay{} + return nil +} + +func githubContributionDayAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *GithubContributionDay) error { + *o = GithubContributionDay{} + return nil +} + +func testGithubContributionDaysHooks(t *testing.T) { + t.Parallel() + + var err error + + ctx := context.Background() + empty := &GithubContributionDay{} + o := &GithubContributionDay{} + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, false); err != nil { + t.Errorf("Unable to randomize GithubContributionDay object: %s", err) + } + + AddGithubContributionDayHook(boil.BeforeInsertHook, githubContributionDayBeforeInsertHook) + if err = o.doBeforeInsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeInsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o) + } + githubContributionDayBeforeInsertHooks = []GithubContributionDayHook{} + + AddGithubContributionDayHook(boil.AfterInsertHook, githubContributionDayAfterInsertHook) + if err = o.doAfterInsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterInsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o) + } + githubContributionDayAfterInsertHooks = []GithubContributionDayHook{} + + AddGithubContributionDayHook(boil.AfterSelectHook, githubContributionDayAfterSelectHook) + if err = o.doAfterSelectHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterSelectHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o) + } + githubContributionDayAfterSelectHooks = []GithubContributionDayHook{} + + AddGithubContributionDayHook(boil.BeforeUpdateHook, githubContributionDayBeforeUpdateHook) + if err = o.doBeforeUpdateHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o) + } + githubContributionDayBeforeUpdateHooks = []GithubContributionDayHook{} + + AddGithubContributionDayHook(boil.AfterUpdateHook, githubContributionDayAfterUpdateHook) + if err = o.doAfterUpdateHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterUpdateHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o) + } + githubContributionDayAfterUpdateHooks = []GithubContributionDayHook{} + + AddGithubContributionDayHook(boil.BeforeDeleteHook, githubContributionDayBeforeDeleteHook) + if err = o.doBeforeDeleteHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o) + } + githubContributionDayBeforeDeleteHooks = []GithubContributionDayHook{} + + AddGithubContributionDayHook(boil.AfterDeleteHook, githubContributionDayAfterDeleteHook) + if err = o.doAfterDeleteHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterDeleteHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o) + } + githubContributionDayAfterDeleteHooks = []GithubContributionDayHook{} + + AddGithubContributionDayHook(boil.BeforeUpsertHook, githubContributionDayBeforeUpsertHook) + if err = o.doBeforeUpsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o) + } + githubContributionDayBeforeUpsertHooks = []GithubContributionDayHook{} + + AddGithubContributionDayHook(boil.AfterUpsertHook, githubContributionDayAfterUpsertHook) + if err = o.doAfterUpsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterUpsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o) + } + githubContributionDayAfterUpsertHooks = []GithubContributionDayHook{} +} + +func testGithubContributionDaysInsert(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } +} + +func testGithubContributionDaysInsertWhitelist(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Whitelist(githubContributionDayColumnsWithoutDefault...)); err != nil { + t.Error(err) + } + + count, err := GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } +} + +func testGithubContributionDayToOneUserUsingUser(t *testing.T) { + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var local GithubContributionDay + var foreign User + + seed := randomize.NewSeed() + if err := randomize.Struct(seed, &local, githubContributionDayDBTypes, false, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + if err := randomize.Struct(seed, &foreign, userDBTypes, false, userColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize User struct: %s", err) + } + + if err := foreign.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + local.UserID = foreign.ID + if err := local.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + check, err := local.User().One(ctx, tx) + if err != nil { + t.Fatal(err) + } + + if check.ID != foreign.ID { + t.Errorf("want: %v, got %v", foreign.ID, check.ID) + } + + slice := GithubContributionDaySlice{&local} + if err = local.L.LoadUser(ctx, tx, false, (*[]*GithubContributionDay)(&slice), nil); err != nil { + t.Fatal(err) + } + if local.R.User == nil { + t.Error("struct should have been eager loaded") + } + + local.R.User = nil + if err = local.L.LoadUser(ctx, tx, true, &local, nil); err != nil { + t.Fatal(err) + } + if local.R.User == nil { + t.Error("struct should have been eager loaded") + } +} + +func testGithubContributionDayToOneSetOpUserUsingUser(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a GithubContributionDay + var b, c User + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, githubContributionDayDBTypes, false, strmangle.SetComplement(githubContributionDayPrimaryKeyColumns, githubContributionDayColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &b, userDBTypes, false, strmangle.SetComplement(userPrimaryKeyColumns, userColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &c, userDBTypes, false, strmangle.SetComplement(userPrimaryKeyColumns, userColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + for i, x := range []*User{&b, &c} { + err = a.SetUser(ctx, tx, i != 0, x) + if err != nil { + t.Fatal(err) + } + + if a.R.User != x { + t.Error("relationship struct not set to correct value") + } + + if x.R.GithubContributionDays[0] != &a { + t.Error("failed to append to foreign relationship struct") + } + if a.UserID != x.ID { + t.Error("foreign key was wrong value", a.UserID) + } + + zero := reflect.Zero(reflect.TypeOf(a.UserID)) + reflect.Indirect(reflect.ValueOf(&a.UserID)).Set(zero) + + if err = a.Reload(ctx, tx); err != nil { + t.Fatal("failed to reload", err) + } + + if a.UserID != x.ID { + t.Error("foreign key was wrong value", a.UserID, x.ID) + } + } +} + +func testGithubContributionDaysReload(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if err = o.Reload(ctx, tx); err != nil { + t.Error(err) + } +} + +func testGithubContributionDaysReloadAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice := GithubContributionDaySlice{o} + + if err = slice.ReloadAll(ctx, tx); err != nil { + t.Error(err) + } +} + +func testGithubContributionDaysSelect(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice, err := GithubContributionDays().All(ctx, tx) + if err != nil { + t.Error(err) + } + + if len(slice) != 1 { + t.Error("want one record, got:", len(slice)) + } +} + +var ( + githubContributionDayDBTypes = map[string]string{`ID`: `bigint`, `Count`: `integer`, `Date`: `date`, `UserID`: `bigint`, `CreatedAt`: `timestamp without time zone`, `UpdatedAt`: `timestamp without time zone`} + _ = bytes.MinRead +) + +func testGithubContributionDaysUpdate(t *testing.T) { + t.Parallel() + + if 0 == len(githubContributionDayPrimaryKeyColumns) { + t.Skip("Skipping table with no primary key columns") + } + if len(githubContributionDayColumns) == len(githubContributionDayPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } + + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only affect one row but affected", rowsAff) + } +} + +func testGithubContributionDaysSliceUpdateAll(t *testing.T) { + t.Parallel() + + if len(githubContributionDayColumns) == len(githubContributionDayPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + o := &GithubContributionDay{} + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } + + if err = randomize.Struct(seed, o, githubContributionDayDBTypes, true, githubContributionDayPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + // Remove Primary keys and unique columns from what we plan to update + var fields []string + if strmangle.StringSliceMatch(githubContributionDayColumns, githubContributionDayPrimaryKeyColumns) { + fields = githubContributionDayColumns + } else { + fields = strmangle.SetComplement( + githubContributionDayColumns, + githubContributionDayPrimaryKeyColumns, + ) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + typ := reflect.TypeOf(o).Elem() + n := typ.NumField() + + updateMap := M{} + for _, col := range fields { + for i := 0; i < n; i++ { + f := typ.Field(i) + if f.Tag.Get("boil") == col { + updateMap[col] = value.Field(i).Interface() + } + } + } + + slice := GithubContributionDaySlice{o} + if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("wanted one record updated but got", rowsAff) + } +} + +func testGithubContributionDaysUpsert(t *testing.T) { + t.Parallel() + + if len(githubContributionDayColumns) == len(githubContributionDayPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + // Attempt the INSERT side of an UPSERT + o := GithubContributionDay{} + if err = randomize.Struct(seed, &o, githubContributionDayDBTypes, true); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Upsert(ctx, tx, false, nil, boil.Infer(), boil.Infer()); err != nil { + t.Errorf("Unable to upsert GithubContributionDay: %s", err) + } + + count, err := GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + if count != 1 { + t.Error("want one record, got:", count) + } + + // Attempt the UPDATE side of an UPSERT + if err = randomize.Struct(seed, &o, githubContributionDayDBTypes, false, githubContributionDayPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize GithubContributionDay struct: %s", err) + } + + if err = o.Upsert(ctx, tx, true, nil, boil.Infer(), boil.Infer()); err != nil { + t.Errorf("Unable to upsert GithubContributionDay: %s", err) + } + + count, err = GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Error(err) + } + if count != 1 { + t.Error("want one record, got:", count) + } +} diff --git a/infra/record/profiles.go b/infra/record/profiles.go index ed34c653..1f60345e 100644 --- a/infra/record/profiles.go +++ b/infra/record/profiles.go @@ -68,15 +68,6 @@ var ProfileColumns = struct { // Generated where -type whereHelperint struct{ field string } - -func (w whereHelperint) EQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } -func (w whereHelperint) NEQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } -func (w whereHelperint) LT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } -func (w whereHelperint) LTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } -func (w whereHelperint) GT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } -func (w whereHelperint) GTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } - type whereHelperbool struct{ field string } func (w whereHelperbool) EQ(x bool) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } diff --git a/infra/record/psql_suites_test.go b/infra/record/psql_suites_test.go index c0f8cdc4..661fe5da 100644 --- a/infra/record/psql_suites_test.go +++ b/infra/record/psql_suites_test.go @@ -12,6 +12,8 @@ func TestUpsert(t *testing.T) { t.Run("Entries", testEntriesUpsert) + t.Run("GithubContributionDays", testGithubContributionDaysUpsert) + t.Run("Invitations", testInvitationsUpsert) t.Run("Profiles", testProfilesUpsert) diff --git a/infra/record/users.go b/infra/record/users.go index 8969d321..644a7f9f 100644 --- a/infra/record/users.go +++ b/infra/record/users.go @@ -91,23 +91,26 @@ var UserWhere = struct { // UserRels is where relationship names are stored. var UserRels = struct { - Profile string - Blogs string - AuthorEntries string - InviterInvitations string + Profile string + Blogs string + AuthorEntries string + GithubContributionDays string + InviterInvitations string }{ - Profile: "Profile", - Blogs: "Blogs", - AuthorEntries: "AuthorEntries", - InviterInvitations: "InviterInvitations", + Profile: "Profile", + Blogs: "Blogs", + AuthorEntries: "AuthorEntries", + GithubContributionDays: "GithubContributionDays", + InviterInvitations: "InviterInvitations", } // userR is where relationships are stored. type userR struct { - Profile *Profile - Blogs BlogSlice - AuthorEntries EntrySlice - InviterInvitations InvitationSlice + Profile *Profile + Blogs BlogSlice + AuthorEntries EntrySlice + GithubContributionDays GithubContributionDaySlice + InviterInvitations InvitationSlice } // NewStruct creates a new relationship struct @@ -456,6 +459,27 @@ func (o *User) AuthorEntries(mods ...qm.QueryMod) entryQuery { return query } +// GithubContributionDays retrieves all the github_contribution_day's GithubContributionDays with an executor. +func (o *User) GithubContributionDays(mods ...qm.QueryMod) githubContributionDayQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.Where("\"github_contribution_days\".\"user_id\"=?", o.ID), + ) + + query := GithubContributionDays(queryMods...) + queries.SetFrom(query.Query, "\"github_contribution_days\"") + + if len(queries.GetSelect(query.Query)) == 0 { + queries.SetSelect(query.Query, []string{"\"github_contribution_days\".*"}) + } + + return query +} + // InviterInvitations retrieves all the invitation's Invitations with an executor via inviter_id column. func (o *User) InviterInvitations(mods ...qm.QueryMod) invitationQuery { var queryMods []qm.QueryMod @@ -772,6 +796,101 @@ func (userL) LoadAuthorEntries(ctx context.Context, e boil.ContextExecutor, sing return nil } +// LoadGithubContributionDays allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (userL) LoadGithubContributionDays(ctx context.Context, e boil.ContextExecutor, singular bool, maybeUser interface{}, mods queries.Applicator) error { + var slice []*User + var object *User + + if singular { + object = maybeUser.(*User) + } else { + slice = *maybeUser.(*[]*User) + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = &userR{} + } + args = append(args, object.ID) + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = &userR{} + } + + for _, a := range args { + if a == obj.ID { + continue Outer + } + } + + args = append(args, obj.ID) + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery(qm.From(`github_contribution_days`), qm.WhereIn(`user_id in ?`, args...)) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load github_contribution_days") + } + + var resultSlice []*GithubContributionDay + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice github_contribution_days") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on github_contribution_days") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for github_contribution_days") + } + + if len(githubContributionDayAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.GithubContributionDays = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = &githubContributionDayR{} + } + foreign.R.User = object + } + return nil + } + + for _, foreign := range resultSlice { + for _, local := range slice { + if local.ID == foreign.UserID { + local.R.GithubContributionDays = append(local.R.GithubContributionDays, foreign) + if foreign.R == nil { + foreign.R = &githubContributionDayR{} + } + foreign.R.User = local + break + } + } + } + + return nil +} + // LoadInviterInvitations allows an eager lookup of values, cached into the // loaded structs of the objects. This is for a 1-M or N-M relationship. func (userL) LoadInviterInvitations(ctx context.Context, e boil.ContextExecutor, singular bool, maybeUser interface{}, mods queries.Applicator) error { @@ -1051,6 +1170,59 @@ func (o *User) AddAuthorEntries(ctx context.Context, exec boil.ContextExecutor, return nil } +// AddGithubContributionDays adds the given related objects to the existing relationships +// of the user, optionally inserting them as new records. +// Appends related to o.R.GithubContributionDays. +// Sets related.R.User appropriately. +func (o *User) AddGithubContributionDays(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*GithubContributionDay) error { + var err error + for _, rel := range related { + if insert { + rel.UserID = o.ID + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } else { + updateQuery := fmt.Sprintf( + "UPDATE \"github_contribution_days\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"user_id"}), + strmangle.WhereClause("\"", "\"", 2, githubContributionDayPrimaryKeyColumns), + ) + values := []interface{}{o.ID, rel.ID} + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, updateQuery) + fmt.Fprintln(boil.DebugWriter, values) + } + + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update foreign table") + } + + rel.UserID = o.ID + } + } + + if o.R == nil { + o.R = &userR{ + GithubContributionDays: related, + } + } else { + o.R.GithubContributionDays = append(o.R.GithubContributionDays, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = &githubContributionDayR{ + User: o, + } + } else { + rel.R.User = o + } + } + return nil +} + // AddInviterInvitations adds the given related objects to the existing relationships // of the user, optionally inserting them as new records. // Appends related to o.R.InviterInvitations. diff --git a/infra/record/users_test.go b/infra/record/users_test.go index 4c2db395..edc0618d 100644 --- a/infra/record/users_test.go +++ b/infra/record/users_test.go @@ -650,6 +650,84 @@ func testUserToManyAuthorEntries(t *testing.T) { } } +func testUserToManyGithubContributionDays(t *testing.T) { + var err error + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a User + var b, c GithubContributionDay + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, userDBTypes, true, userColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize User struct: %s", err) + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + if err = randomize.Struct(seed, &b, githubContributionDayDBTypes, false, githubContributionDayColumnsWithDefault...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &c, githubContributionDayDBTypes, false, githubContributionDayColumnsWithDefault...); err != nil { + t.Fatal(err) + } + + b.UserID = a.ID + c.UserID = a.ID + + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + check, err := a.GithubContributionDays().All(ctx, tx) + if err != nil { + t.Fatal(err) + } + + bFound, cFound := false, false + for _, v := range check { + if v.UserID == b.UserID { + bFound = true + } + if v.UserID == c.UserID { + cFound = true + } + } + + if !bFound { + t.Error("expected to find b") + } + if !cFound { + t.Error("expected to find c") + } + + slice := UserSlice{&a} + if err = a.L.LoadGithubContributionDays(ctx, tx, false, (*[]*User)(&slice), nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.GithubContributionDays); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + a.R.GithubContributionDays = nil + if err = a.L.LoadGithubContributionDays(ctx, tx, true, &a, nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.GithubContributionDays); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + if t.Failed() { + t.Logf("%#v", check) + } +} + func testUserToManyInviterInvitations(t *testing.T) { var err error ctx := context.Background() @@ -878,6 +956,81 @@ func testUserToManyAddOpAuthorEntries(t *testing.T) { } } } +func testUserToManyAddOpGithubContributionDays(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a User + var b, c, d, e GithubContributionDay + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, userDBTypes, false, strmangle.SetComplement(userPrimaryKeyColumns, userColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + foreigners := []*GithubContributionDay{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, githubContributionDayDBTypes, false, strmangle.SetComplement(githubContributionDayPrimaryKeyColumns, githubContributionDayColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + foreignersSplitByInsertion := [][]*GithubContributionDay{ + {&b, &c}, + {&d, &e}, + } + + for i, x := range foreignersSplitByInsertion { + err = a.AddGithubContributionDays(ctx, tx, i != 0, x...) + if err != nil { + t.Fatal(err) + } + + first := x[0] + second := x[1] + + if a.ID != first.UserID { + t.Error("foreign key was wrong value", a.ID, first.UserID) + } + if a.ID != second.UserID { + t.Error("foreign key was wrong value", a.ID, second.UserID) + } + + if first.R.User != &a { + t.Error("relationship was not added properly to the foreign slice") + } + if second.R.User != &a { + t.Error("relationship was not added properly to the foreign slice") + } + + if a.R.GithubContributionDays[i*2] != first { + t.Error("relationship struct slice not set to correct value") + } + if a.R.GithubContributionDays[i*2+1] != second { + t.Error("relationship struct slice not set to correct value") + } + + count, err := a.GithubContributionDays().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if want := int64((i + 1) * 2); count != want { + t.Error("want", want, "got", count) + } + } +} func testUserToManyAddOpInviterInvitations(t *testing.T) { var err error diff --git a/infra/store/department/department_store.go b/infra/store/department/department_store.go index fb05abef..a90cce6d 100644 --- a/infra/store/department/department_store.go +++ b/infra/store/department/department_store.go @@ -2,21 +2,21 @@ package departmentstore import ( "context" - "database/sql" "github.com/pkg/errors" "github.com/ProgrammingLab/prolab-accounts/infra/record" "github.com/ProgrammingLab/prolab-accounts/infra/store" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) type departmentStoreImpl struct { ctx context.Context - db *sql.DB + db *sqlutil.DB } // NewDepartmentStore returns new entry department store -func NewDepartmentStore(ctx context.Context, db *sql.DB) store.DepartmentStore { +func NewDepartmentStore(ctx context.Context, db *sqlutil.DB) store.DepartmentStore { return &departmentStoreImpl{ ctx: ctx, db: db, diff --git a/infra/store/entry/entry_store.go b/infra/store/entry/entry_store.go index 1d9bc01d..9aa525e7 100644 --- a/infra/store/entry/entry_store.go +++ b/infra/store/entry/entry_store.go @@ -13,19 +13,19 @@ import ( "github.com/volatiletech/sqlboiler/boil" "github.com/volatiletech/sqlboiler/queries/qm" - "github.com/ProgrammingLab/prolab-accounts/app/util" "github.com/ProgrammingLab/prolab-accounts/infra/record" "github.com/ProgrammingLab/prolab-accounts/infra/store" "github.com/ProgrammingLab/prolab-accounts/model" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) type entryStoreImpl struct { ctx context.Context - db *sql.DB + db *sqlutil.DB } // NewEntryStore returns new entry blog store -func NewEntryStore(ctx context.Context, db *sql.DB) store.EntryStore { +func NewEntryStore(ctx context.Context, db *sqlutil.DB) store.EntryStore { return &entryStoreImpl{ ctx: ctx, db: db, @@ -55,82 +55,71 @@ func (s *entryStoreImpl) ListPublicEntries(before time.Time, limit int) ([]*reco return e[:limit], e[limit].PublishedAt, nil } -func (s *entryStoreImpl) CreateEntries(blog *record.Blog, feed *gofeed.Feed) (n int64, err error) { +func (s *entryStoreImpl) CreateEntries(blog *record.Blog, feed *gofeed.Feed) (int64, error) { rev := make([]*gofeed.Item, len(feed.Items)) for i, item := range feed.Items { rev[len(rev)-1-i] = item } feed.Items = rev - tx, err := s.db.Begin() - if err != nil { - return 0, errors.WithStack(err) - } - defer func() { - if e := util.ErrorFromRecover(recover()); e != nil { - _ = tx.Rollback() - err = e + var n int64 + err := s.db.Watch(s.ctx, func(ctx context.Context, tx *sql.Tx) error { + mods := []qm.QueryMod{ + qm.Select(record.EntryColumns.ID, record.EntryColumns.GUID), + qm.Where("blog_id = ?", blog.ID), } - }() - - mods := []qm.QueryMod{ - qm.Select(record.EntryColumns.ID, record.EntryColumns.GUID), - qm.Where("blog_id = ?", blog.ID), - } - entries, err := record.Entries(mods...).All(s.ctx, tx) - if err != nil { - _ = tx.Rollback() - return 0, errors.WithStack(err) - } - - exists := make(map[string]struct{}) - for _, e := range entries { - exists[e.GUID] = struct{}{} - } - - n = 0 - for _, item := range feed.Items { - guid, err := getGUID(blog.ID, item.GUID) + entries, err := record.Entries(mods...).All(s.ctx, tx) if err != nil { - _ = tx.Rollback() - return 0, err + return errors.WithStack(err) } - _, ok := exists[guid] - if ok { - continue + exists := make(map[string]struct{}) + for _, e := range entries { + exists[e.GUID] = struct{}{} } - e := &record.Entry{ - Title: item.Title, - Description: item.Description, - Content: item.Content, - Link: item.Link, - AuthorID: blog.UserID, - GUID: guid, - BlogID: blog.ID, - } - if i := item.Image; i != nil { - e.ImageURL = i.URL - } - if t := item.PublishedParsed; t == nil { - e.PublishedAt = time.Now().In(boil.GetLocation()) - } else { - e.PublishedAt = t.In(boil.GetLocation()) + n = 0 + for _, item := range feed.Items { + guid, err := getGUID(blog.ID, item.GUID) + if err != nil { + return err + } + + _, ok := exists[guid] + if ok { + continue + } + + e := &record.Entry{ + Title: item.Title, + Description: item.Description, + Content: item.Content, + Link: item.Link, + AuthorID: blog.UserID, + GUID: guid, + BlogID: blog.ID, + } + if i := item.Image; i != nil { + e.ImageURL = i.URL + } + if t := item.PublishedParsed; t == nil { + e.PublishedAt = time.Now().In(boil.GetLocation()) + } else { + e.PublishedAt = t.In(boil.GetLocation()) + } + + err = e.Insert(s.ctx, tx, boil.Infer()) + if err != nil { + return errors.WithStack(err) + } + n++ } - err = e.Insert(s.ctx, tx, boil.Infer()) - if err != nil { - _ = tx.Rollback() - return 0, errors.WithStack(err) - } - n++ - } + return nil + }) - err = tx.Commit() if err != nil { - _ = tx.Rollback() - return 0, errors.WithStack(err) + return 0, err } return n, nil } diff --git a/infra/store/github/github_store.go b/infra/store/github/github_store.go new file mode 100644 index 00000000..bc6ff8c4 --- /dev/null +++ b/infra/store/github/github_store.go @@ -0,0 +1,201 @@ +package githubstore + +import ( + "context" + "database/sql" + "fmt" + "strconv" + "strings" + "time" + + "github.com/go-redis/redis" + "github.com/pkg/errors" + "github.com/volatiletech/sqlboiler/boil" + "github.com/volatiletech/sqlboiler/queries/qm" + + "github.com/ProgrammingLab/prolab-accounts/infra/record" + "github.com/ProgrammingLab/prolab-accounts/infra/store" + "github.com/ProgrammingLab/prolab-accounts/model" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" +) + +type githubStoreImpl struct { + ctx context.Context + db *sqlutil.DB + cli *redis.Client +} + +// NewGitHubStore returns new github store +func NewGitHubStore(ctx context.Context, db *sqlutil.DB, cli *redis.Client) store.GitHubStore { + return &githubStoreImpl{ + ctx: ctx, + db: db, + cli: cli, + } +} + +const ( + contributionTotalCountKey = "contributions-total-count" +) + +func (s *githubStoreImpl) UpdateContributionDays(c *model.GitHubContributionCollection) ([]*record.GithubContributionDay, error) { + z := redis.Z{ + Score: float64(c.TotalCount), + Member: int64(c.UserID), + } + err := s.cli.ZAdd(contributionTotalCountKey, z).Err() + if err != nil { + return nil, errors.WithStack(err) + } + + days := c.Days + err = s.db.Watch(s.ctx, func(ctx context.Context, tx *sql.Tx) error { + q := record.GithubContributionDayWhere.UserID.EQ(int64(c.UserID)) + _, err := record.GithubContributionDays(q).DeleteAll(ctx, tx) + if err != nil { + return errors.WithStack(err) + } + + return bulkInsert(ctx, tx, days) + }) + + if err != nil { + return nil, err + } + + return days, nil +} + +func (s *githubStoreImpl) ListContributionCollections(usersLimit int) ([]*model.GitHubContributionCollection, error) { + cols, err := s.getTopUsers(usersLimit) + if err != nil { + return nil, err + } + + err = s.loadDays(cols) + if err != nil { + return nil, err + } + + return cols, nil +} + +func (s *githubStoreImpl) getTopUsers(usersLimit int) ([]*model.GitHubContributionCollection, error) { + values, err := s.cli.ZRevRangeWithScores(contributionTotalCountKey, 0, int64(usersLimit)-1).Result() + if err != nil { + return nil, errors.WithStack(err) + } + + cols := make([]*model.GitHubContributionCollection, 0, len(values)) + for _, v := range values { + id, err := strconv.ParseInt(v.Member.(string), 10, 64) + if err != nil { + return nil, errors.WithStack(err) + } + col := &model.GitHubContributionCollection{ + UserID: model.UserID(id), + TotalCount: int(v.Score), + } + cols = append(cols, col) + } + + return cols, nil +} + +func (s *githubStoreImpl) loadDays(cols []*model.GitHubContributionCollection) error { + userIDs := make([]int64, 0, len(cols)) + for _, c := range cols { + userIDs = append(userIDs, int64(c.UserID)) + } + + mods := []qm.QueryMod{ + qm.Load("User.Profile.Department"), + qm.Load("User.Profile.Role"), + qm.From("github_contribution_days as days"), + qm.InnerJoin("users on users.id = days.user_id"), + qm.InnerJoin("profiles on profiles.id = users.profile_id"), + qm.Where("profiles.profile_scope = ?", model.Public), + qm.WhereIn("users.id in ?", sqlutil.Int64SliceToAbstractSlice(userIDs)...), + qm.OrderBy("days.date"), + } + var days []*record.GithubContributionDay + err := record.NewQuery(mods...).Bind(s.ctx, s.db, &days) + if err != nil { + return errors.WithStack(err) + } + + daysMap := make(map[model.UserID][]*record.GithubContributionDay, len(cols)) + for _, d := range days { + id := model.UserID(d.UserID) + daysMap[id] = append(daysMap[id], d) + } + + for _, c := range cols { + c.Days = daysMap[c.UserID] + } + + return nil +} + +func bulkInsert(ctx context.Context, tx *sql.Tx, days []*record.GithubContributionDay) error { + if len(days) == 0 { + return nil + } + + q := &strings.Builder{} + _, err := q.WriteString("INSERT INTO " + record.TableNames.GithubContributionDays + + " (count, date, user_id, created_at, updated_at) VALUES ") + if err != nil { + return errors.WithStack(err) + } + + verbs, v := insertValueQuery(1) + _, err = q.WriteString(v) + if err != nil { + return errors.WithStack(err) + } + for i := 0; i < len(days)-1; i++ { + nxt, v := insertValueQuery(verbs) + verbs = nxt + _, err := q.WriteString(", " + v) + if err != nil { + return errors.WithStack(err) + } + } + _, err = q.WriteString(";") + if err != nil { + return errors.WithStack(err) + } + + stmt, err := tx.Prepare(q.String()) + if err != nil { + return errors.WithStack(err) + } + + values := make([]interface{}, 0, len(days)*5) + now := time.Now().In(boil.GetLocation()) + for _, d := range days { + values = append(values, insertValues(d, now, now)...) + } + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, q.String()) + fmt.Fprintln(boil.DebugWriter, values) + } + _, err = stmt.ExecContext(ctx, values...) + return errors.WithStack(err) +} + +func insertValueQuery(verbs int) (int, string) { + return verbs + 5, fmt.Sprintf("($%v, $%v, $%v, $%v, $%v)", verbs, verbs+1, verbs+2, verbs+3, verbs+4) +} + +func insertValues(d *record.GithubContributionDay, createdAt, updatedAt time.Time) []interface{} { + return []interface{}{ + d.Count, + d.Date, + d.UserID, + createdAt, + updatedAt, + } +} diff --git a/infra/store/github_store.go b/infra/store/github_store.go new file mode 100644 index 00000000..25fe3888 --- /dev/null +++ b/infra/store/github_store.go @@ -0,0 +1,12 @@ +package store + +import ( + "github.com/ProgrammingLab/prolab-accounts/infra/record" + "github.com/ProgrammingLab/prolab-accounts/model" +) + +// GitHubStore provides github data +type GitHubStore interface { + UpdateContributionDays(c *model.GitHubContributionCollection) ([]*record.GithubContributionDay, error) + ListContributionCollections(usersLimit int) ([]*model.GitHubContributionCollection, error) +} diff --git a/infra/store/invitation/invitation_store.go b/infra/store/invitation/invitation_store.go index 3113b022..355d2a28 100644 --- a/infra/store/invitation/invitation_store.go +++ b/infra/store/invitation/invitation_store.go @@ -14,15 +14,16 @@ import ( "github.com/ProgrammingLab/prolab-accounts/infra/record" "github.com/ProgrammingLab/prolab-accounts/infra/store" "github.com/ProgrammingLab/prolab-accounts/model" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) type invitationStoreImpl struct { ctx context.Context - db *sql.DB + db *sqlutil.DB } // NewInvitationStore returns new invitation store -func NewInvitationStore(ctx context.Context, db *sql.DB) store.InvitationStore { +func NewInvitationStore(ctx context.Context, db *sqlutil.DB) store.InvitationStore { return &invitationStoreImpl{ ctx: ctx, db: db, diff --git a/infra/store/profile/profile_store.go b/infra/store/profile/profile_store.go index 3a681079..75888120 100644 --- a/infra/store/profile/profile_store.go +++ b/infra/store/profile/profile_store.go @@ -8,64 +8,49 @@ import ( "github.com/volatiletech/null" "github.com/volatiletech/sqlboiler/boil" - "github.com/ProgrammingLab/prolab-accounts/app/util" "github.com/ProgrammingLab/prolab-accounts/infra/record" "github.com/ProgrammingLab/prolab-accounts/infra/store" "github.com/ProgrammingLab/prolab-accounts/model" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) type profileStoreImpl struct { ctx context.Context - db *sql.DB + db *sqlutil.DB } // NewProfileStore returns new profile store -func NewProfileStore(ctx context.Context, db *sql.DB) store.ProfileStore { +func NewProfileStore(ctx context.Context, db *sqlutil.DB) store.ProfileStore { return &profileStoreImpl{ ctx: ctx, db: db, } } -func (s *profileStoreImpl) CreateOrUpdateProfile(userID model.UserID, profile *record.Profile) (err error) { - tx, err := s.db.Begin() - if err != nil { - return errors.WithStack(err) - } - defer func() { - if e := util.ErrorFromRecover(recover()); e != nil { - _ = tx.Rollback() - err = e +func (s *profileStoreImpl) CreateOrUpdateProfile(userID model.UserID, profile *record.Profile) error { + err := s.db.Watch(s.ctx, func(ctx context.Context, tx *sql.Tx) error { + if profile.ID == 0 { + err := profile.Insert(s.ctx, tx, boil.Infer()) + if err != nil { + return errors.WithStack(err) + } + u := record.User{ + ID: int64(userID), + ProfileID: null.Int64From(profile.ID), + } + _, err = u.Update(s.ctx, tx, boil.Whitelist("profile_id", "updated_at")) + if err != nil { + return errors.WithStack(err) + } + } else { + _, err := profile.Update(s.ctx, tx, boil.Infer()) + if err != nil { + return errors.WithStack(err) + } } - }() - if profile.ID == 0 { - err = profile.Insert(s.ctx, tx, boil.Infer()) - if err != nil { - _ = tx.Rollback() - return errors.WithStack(err) - } - u := record.User{ - ID: int64(userID), - ProfileID: null.Int64From(profile.ID), - } - _, err = u.Update(s.ctx, tx, boil.Whitelist("profile_id", "updated_at")) - if err != nil { - _ = tx.Rollback() - return errors.WithStack(err) - } - } else { - _, err = profile.Update(s.ctx, tx, boil.Infer()) - if err != nil { - _ = tx.Rollback() - return errors.WithStack(err) - } - } + return nil + }) - err = tx.Commit() - if err != nil { - _ = tx.Rollback() - return errors.WithStack(err) - } - return nil + return err } diff --git a/infra/store/role/role_store.go b/infra/store/role/role_store.go index 4ea154b5..5b876eab 100644 --- a/infra/store/role/role_store.go +++ b/infra/store/role/role_store.go @@ -2,21 +2,21 @@ package rolestore import ( "context" - "database/sql" "github.com/pkg/errors" "github.com/ProgrammingLab/prolab-accounts/infra/record" "github.com/ProgrammingLab/prolab-accounts/infra/store" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) type roleStoreImpl struct { ctx context.Context - db *sql.DB + db *sqlutil.DB } // NewRoleStore returns new role store -func NewRoleStore(ctx context.Context, db *sql.DB) store.RoleStore { +func NewRoleStore(ctx context.Context, db *sqlutil.DB) store.RoleStore { return &roleStoreImpl{ ctx: ctx, db: db, diff --git a/infra/store/session/session_store.go b/infra/store/session/session_store.go index 1f060804..d0a29a6d 100644 --- a/infra/store/session/session_store.go +++ b/infra/store/session/session_store.go @@ -15,12 +15,13 @@ import ( "github.com/ProgrammingLab/prolab-accounts/infra/record" "github.com/ProgrammingLab/prolab-accounts/infra/store" "github.com/ProgrammingLab/prolab-accounts/model" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) type sessionStoreImpl struct { ctx context.Context client *redis.Client - db *sql.DB + db *sqlutil.DB } const ( @@ -29,7 +30,7 @@ const ( ) // NewSessionStore returns new session store -func NewSessionStore(ctx context.Context, cli *redis.Client, db *sql.DB) store.SessionStore { +func NewSessionStore(ctx context.Context, cli *redis.Client, db *sqlutil.DB) store.SessionStore { return &sessionStoreImpl{ ctx: ctx, client: cli, diff --git a/infra/store/user/user_store.go b/infra/store/user/user_store.go index 7fb203ca..83af8c13 100644 --- a/infra/store/user/user_store.go +++ b/infra/store/user/user_store.go @@ -17,21 +17,21 @@ import ( "github.com/volatiletech/sqlboiler/boil" "github.com/volatiletech/sqlboiler/queries/qm" - "github.com/ProgrammingLab/prolab-accounts/app/util" "github.com/ProgrammingLab/prolab-accounts/infra/record" "github.com/ProgrammingLab/prolab-accounts/infra/store" "github.com/ProgrammingLab/prolab-accounts/model" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) type userStoreImpl struct { ctx context.Context - db *sql.DB + db *sqlutil.DB cli *minio.Client bucketName string } // NewUserStore returns new user store -func NewUserStore(ctx context.Context, db *sql.DB, cli *minio.Client, bucket string) store.UserStore { +func NewUserStore(ctx context.Context, db *sqlutil.DB, cli *minio.Client, bucket string) store.UserStore { return &userStoreImpl{ ctx: ctx, db: db, @@ -124,33 +124,21 @@ func (s *userStoreImpl) ListPublicUsers(minUserID model.UserID, limit int) ([]*r return u[:limit], model.UserID(u[limit].ID), nil } -func (s *userStoreImpl) UpdateFullName(userID model.UserID, fullName string) (u *record.User, err error) { - tx, err := s.db.Begin() - if err != nil { - return nil, errors.WithStack(err) - } - defer func() { - if err = util.ErrorFromRecover(recover()); err != nil { - _ = tx.Rollback() +func (s *userStoreImpl) UpdateFullName(userID model.UserID, fullName string) (*record.User, error) { + var u *record.User + err := s.db.Watch(s.ctx, func(ctx context.Context, tx *sql.Tx) error { + var err error + u, err = record.FindUser(s.ctx, tx, int64(userID)) + if err != nil { + return errors.WithStack(err) } - }() - u, err = record.FindUser(s.ctx, tx, int64(userID)) - if err != nil { - _ = tx.Rollback() - return nil, errors.WithStack(err) - } - u.FullName = fullName - _, err = u.Update(s.ctx, tx, boil.Whitelist(record.UserColumns.FullName, record.UserColumns.UpdatedAt)) + u.FullName = fullName + _, err = u.Update(s.ctx, tx, boil.Whitelist(record.UserColumns.FullName, record.UserColumns.UpdatedAt)) + return errors.WithStack(err) + }) if err != nil { - _ = tx.Rollback() - return nil, errors.WithStack(err) - } - - err = tx.Commit() - if err != nil { - _ = tx.Rollback() - return nil, errors.WithStack(err) + return nil, err } return u, nil diff --git a/infra/store/user_blog/user_blog_store.go b/infra/store/user_blog/user_blog_store.go index 2ce414e4..2dc1cbd4 100644 --- a/infra/store/user_blog/user_blog_store.go +++ b/infra/store/user_blog/user_blog_store.go @@ -7,18 +7,18 @@ import ( "github.com/pkg/errors" "github.com/volatiletech/sqlboiler/boil" - "github.com/ProgrammingLab/prolab-accounts/app/util" "github.com/ProgrammingLab/prolab-accounts/infra/record" "github.com/ProgrammingLab/prolab-accounts/infra/store" + "github.com/ProgrammingLab/prolab-accounts/sqlutil" ) type userBlogStoreImpl struct { ctx context.Context - db *sql.DB + db *sqlutil.DB } // NewUserBlogStore returns new user blog store -func NewUserBlogStore(ctx context.Context, db *sql.DB) store.UserBlogStore { +func NewUserBlogStore(ctx context.Context, db *sqlutil.DB) store.UserBlogStore { return &userBlogStoreImpl{ ctx: ctx, db: db, @@ -51,72 +51,32 @@ func (s *userBlogStoreImpl) CreateUserBlog(blog *record.Blog) error { return nil } -func (s *userBlogStoreImpl) UpdateUserBlog(blog *record.Blog) (err error) { - tx, err := s.db.Begin() - if err != nil { - return errors.WithStack(err) - } - defer func() { - if e := util.ErrorFromRecover(recover()); e != nil { - _ = tx.Rollback() - err = e +func (s *userBlogStoreImpl) UpdateUserBlog(blog *record.Blog) error { + err := s.db.Watch(s.ctx, func(ctx context.Context, tx *sql.Tx) error { + exists, err := record.FindBlog(s.ctx, tx, blog.ID) + if err != nil { + return errors.WithStack(err) + } + if exists.UserID != blog.UserID { + return sql.ErrNoRows } - }() - - exists, err := record.FindBlog(s.ctx, tx, blog.ID) - if err != nil { - _ = tx.Rollback() - return errors.WithStack(err) - } - if exists.UserID != blog.UserID { - _ = tx.Rollback() - return sql.ErrNoRows - } - - _, err = blog.Update(s.ctx, tx, boil.Infer()) - if err != nil { - _ = tx.Rollback() - return errors.WithStack(err) - } - err = tx.Commit() - if err != nil { - _ = tx.Rollback() + _, err = blog.Update(s.ctx, tx, boil.Infer()) return errors.WithStack(err) - } - - return nil + }) + return err } -func (s *userBlogStoreImpl) DeleteUserBlog(blogID int64) (err error) { - tx, err := s.db.Begin() - if err != nil { - return errors.WithStack(err) - } - defer func() { - if e := util.ErrorFromRecover(recover()); e != nil { +func (s *userBlogStoreImpl) DeleteUserBlog(blogID int64) error { + err := s.db.Watch(s.ctx, func(ctx context.Context, tx *sql.Tx) error { + _, err := record.Entries(record.EntryWhere.BlogID.EQ(blogID)).DeleteAll(s.ctx, tx) + if err != nil { _ = tx.Rollback() - err = e + return errors.WithStack(err) } - }() - - _, err = record.Entries(record.EntryWhere.BlogID.EQ(blogID)).DeleteAll(s.ctx, tx) - if err != nil { - _ = tx.Rollback() - return errors.WithStack(err) - } - _, err = record.Blogs(record.BlogWhere.ID.EQ(blogID)).DeleteAll(s.ctx, tx) - if err != nil { - _ = tx.Rollback() - return errors.WithStack(err) - } - - err = tx.Commit() - if err != nil { - _ = tx.Rollback() + _, err = record.Blogs(record.BlogWhere.ID.EQ(blogID)).DeleteAll(s.ctx, tx) return errors.WithStack(err) - } - - return nil + }) + return err } diff --git a/model/github.go b/model/github.go new file mode 100644 index 00000000..10e324f9 --- /dev/null +++ b/model/github.go @@ -0,0 +1,12 @@ +package model + +import ( + "github.com/ProgrammingLab/prolab-accounts/infra/record" +) + +// GitHubContributionCollection represents collection of github contribution +type GitHubContributionCollection struct { + UserID UserID + TotalCount int + Days []*record.GithubContributionDay +} diff --git a/sqlutil/conv.go b/sqlutil/conv.go new file mode 100644 index 00000000..e63080eb --- /dev/null +++ b/sqlutil/conv.go @@ -0,0 +1,19 @@ +package sqlutil + +// Int32SliceToInt64Slice converts int32 array to int64 array +func Int32SliceToInt64Slice(a []int32) []int64 { + res := make([]int64, 0, len(a)) + for _, v := range a { + res = append(res, int64(v)) + } + return res +} + +// Int64SliceToAbstractSlice converts int64 array to []interface{} +func Int64SliceToAbstractSlice(a []int64) []interface{} { + res := []interface{}{} + for _, v := range a { + res = append(res, v) + } + return res +} diff --git a/sqlutil/db.go b/sqlutil/db.go new file mode 100644 index 00000000..ddadefd7 --- /dev/null +++ b/sqlutil/db.go @@ -0,0 +1,51 @@ +package sqlutil + +import ( + "context" + "database/sql" + + "github.com/pkg/errors" + + "github.com/ProgrammingLab/prolab-accounts/app/util" +) + +// DB is database with util +type DB struct { + *sql.DB +} + +// New creates new db +func New(db *sql.DB) *DB { + return &DB{ + DB: db, + } +} + +// Watch calls f with transaction +func (d *DB) Watch(ctx context.Context, f func(ctx context.Context, tx *sql.Tx) error) (err error) { + tx, err := d.Begin() + if err != nil { + return errors.WithStack(err) + } + + defer func() { + if e := util.ErrorFromRecover(recover()); e != nil { + err = e + _ = tx.Rollback() + } + }() + + err = f(ctx, tx) + if err != nil { + _ = tx.Rollback() + return err + } + + err = tx.Commit() + if err != nil { + _ = tx.Rollback() + return errors.WithStack(err) + } + + return nil +}