From 7e5f7e949531222099182cd5906c1b2e5f3a4b9e Mon Sep 17 00:00:00 2001 From: omkar sonawane Date: Sat, 5 Jul 2025 18:53:57 +0530 Subject: [PATCH 01/10] Update .sample.env with enhanced configuration options for PostgreSQL, Supabase, Cloudinary, caching, event publishing, webhooks, and Razorpay. Removed deprecated entries and added placeholders for user-specific values. --- .sample.env | 69 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/.sample.env b/.sample.env index c8e85af..015c0c1 100644 --- a/.sample.env +++ b/.sample.env @@ -1,27 +1,54 @@ # Server Configuration -CAYGNUS_SERVER_ENV=local CAYGNUS_SERVER_ADDRESS=:8080 - +CAYGNUS_SERVER_ENV=dev # Logging Configuration +# Options: debug, info, warn, error, fatal, panic CAYGNUS_LOGGING_LEVEL=info -# postgress -CAYGNUS_POSTGRES_HOST=xxx -CAYGNUS_POSTGRES_PORT=xxx -CAYGNUS_POSTGRES_USER=xxx -CAYGNUS_POSTGRES_PASSWORD=xxx -CAYGNUS_POSTGRES_DBNAME=xxx -CAYGNUS_POSTGRES_SSLMODE=xxx -CAYGNUS_POSTGRES_MAX_OPEN_CONNS=xxx -CAYGNUS_POSTGRES_MAX_IDLE_CONNS=xxx -CAYGNUS_POSTGRES_CONN_MAX_LIFETIME_MINUTES=xxx -CAYGNUS_POSTGRES_AUTO_MIGRATE=xxx - -# Supabase Configuration -CAYGNUS_SUPABASE_URL=xxx -CAYGNUS_SUPABASE_KEY=xxx -CAYGNUS_SUPABASE_JWT_SECRET=xxx -CAYGNUS_SUPABASE_SERVICE_KEY=xxx - -CAYGNUS_SECRETS_ENCRYPTION_KEY=xxxxxx \ No newline at end of file +# PostgreSQL Config +CAYGNUS_POSTGRES_HOST= +CAYGNUS_POSTGRES_PORT=5432 +CAYGNUS_POSTGRES_USER= +CAYGNUS_POSTGRES_PASSWORD= +CAYGNUS_POSTGRES_DBNAME= +CAYGNUS_POSTGRES_SSLMODE=require +CAYGNUS_POSTGRES_MAX_OPEN_CONNS=10 +CAYGNUS_POSTGRES_MAX_IDLE_CONNS=5 +CAYGNUS_POSTGRES_CONN_MAX_LIFETIME_MINUTES=60 +CAYGNUS_POSTGRES_AUTO_MIGRATE=false + +# Supabase Config +CAYGNUS_SUPABASE_URL= +CAYGNUS_SUPABASE_KEY= +CAYGNUS_SUPABASE_JWT_SECRET= +CAYGNUS_SUPABASE_SERVICE_KEY= + +# Encryption Configuration +CAYGNUS_SECRETS_ENCRYPTION_KEY= + +# Cloudinary Configs +CAYGNUS_CLOUDINARY_CLOUD_NAME= +CAYGNUS_CLOUDINARY_API_KEY= +CAYGNUS_CLOUDINARY_API_SECRET= +CAYGNUS_CLOUDINARY_API_BASE_URL=https://api.cloudinary.com # Optional + +# Cache Configuration +CAYGNUS_CACHE_ENABLED=true + +# Event Publisher Configuration +CAYGNUS_EVENT_PUBLISH_DESTINATION=kafka + +# Webhook Configuration +CAYGNUS_WEBHOOK_ENABLED=true +CAYGNUS_WEBHOOK_TOPIC=internal_events +CAYGNUS_WEBHOOK_PUBSUB=memory +CAYGNUS_WEBHOOK_MAX_RETRIES=3 +CAYGNUS_WEBHOOK_INITIAL_INTERVAL=1s +CAYGNUS_WEBHOOK_MAX_INTERVAL=10s +CAYGNUS_WEBHOOK_MULTIPLIER=2.0 +CAYGNUS_WEBHOOK_MAX_ELAPSED_TIME=2m + +# Razorpay Config +CAYGNUS_RAZORPAY_API_KEY= +CAYGNUS_RAZORPAY_API_SECRET= From 39fc927c18adf1c5bb59219dbd201f44129436e7 Mon Sep 17 00:00:00 2001 From: omkar sonawane Date: Sat, 5 Jul 2025 18:57:22 +0530 Subject: [PATCH 02/10] Update README.md to reflect repository URL change from omkar273/codegeeky to Caygnus/codegeeky-lms-backend --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e46e937..038b993 100644 --- a/README.md +++ b/README.md @@ -560,8 +560,8 @@ Whether you're a newbie coder or a wizard 🧙‍♀️, your perspective is gol ## Contributors - - + + ## Repo Activity From 61c944f22df8a887ccf84b7bb3f636f745d7cfae Mon Sep 17 00:00:00 2001 From: omkar sonawane Date: Sat, 5 Jul 2025 23:24:00 +0530 Subject: [PATCH 03/10] Add cart functionality with user-specific features --- cmd/server/main.go | 3 + ent/cart.go | 11 +- ent/cart_create.go | 2 +- ent/mutation.go | 2 +- ent/schema/cart.go | 1 + internal/api/dto/cart.go | 123 ++++++ internal/api/v1/cart.go | 1 + internal/domain/cart/model.go | 2 +- internal/domain/cart/repository.go | 3 + internal/repository/ent/cart.go | 43 ++- internal/repository/factory.go | 5 + internal/service/cart.go | 465 +++++++++++++++++++++++ internal/service/factory.go | 3 +- internal/testutil/inmemory_cart_store.go | 36 ++ internal/types/cart.go | 6 +- 15 files changed, 692 insertions(+), 14 deletions(-) create mode 100644 internal/api/dto/cart.go create mode 100644 internal/api/v1/cart.go diff --git a/cmd/server/main.go b/cmd/server/main.go index dfc6a14..6d7dc85 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -95,6 +95,9 @@ func main() { // internship batch repository repository.NewInternshipBatchRepository, + // cart repository + repository.NewCartRepository, + // pubsub router pubsubRouter.NewRouter, ), diff --git a/ent/cart.go b/ent/cart.go index a912d9d..6dbc09c 100644 --- a/ent/cart.go +++ b/ent/cart.go @@ -45,7 +45,7 @@ type Cart struct { // Total holds the value of the "total" field. Total decimal.Decimal `json:"total,omitempty"` // ExpiresAt holds the value of the "expires_at" field. - ExpiresAt time.Time `json:"expires_at,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the CartQuery when eager-loading is set. Edges CartEdges `json:"edges"` @@ -195,7 +195,8 @@ func (c *Cart) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field expires_at", values[i]) } else if value.Valid { - c.ExpiresAt = value.Time + c.ExpiresAt = new(time.Time) + *c.ExpiresAt = value.Time } default: c.selectValues.Set(columns[i], values[i]) @@ -279,8 +280,10 @@ func (c *Cart) String() string { builder.WriteString("total=") builder.WriteString(fmt.Sprintf("%v", c.Total)) builder.WriteString(", ") - builder.WriteString("expires_at=") - builder.WriteString(c.ExpiresAt.Format(time.ANSIC)) + if v := c.ExpiresAt; v != nil { + builder.WriteString("expires_at=") + builder.WriteString(v.Format(time.ANSIC)) + } builder.WriteByte(')') return builder.String() } diff --git a/ent/cart_create.go b/ent/cart_create.go index 541fdb5..32408c3 100644 --- a/ent/cart_create.go +++ b/ent/cart_create.go @@ -406,7 +406,7 @@ func (cc *CartCreate) createSpec() (*Cart, *sqlgraph.CreateSpec) { } if value, ok := cc.mutation.ExpiresAt(); ok { _spec.SetField(cart.FieldExpiresAt, field.TypeTime, value) - _node.ExpiresAt = value + _node.ExpiresAt = &value } if nodes := cc.mutation.LineItemsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ diff --git a/ent/mutation.go b/ent/mutation.go index 1f7f20e..0e0eaa0 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -671,7 +671,7 @@ func (m *CartMutation) ExpiresAt() (r time.Time, exists bool) { // OldExpiresAt returns the old "expires_at" field's value of the Cart entity. // If the Cart object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *CartMutation) OldExpiresAt(ctx context.Context) (v time.Time, err error) { +func (m *CartMutation) OldExpiresAt(ctx context.Context) (v *time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations") } diff --git a/ent/schema/cart.go b/ent/schema/cart.go index 7d0c697..71bdcb4 100644 --- a/ent/schema/cart.go +++ b/ent/schema/cart.go @@ -76,6 +76,7 @@ func (Cart) Fields() []ent.Field { SchemaType(map[string]string{ "postgres": "timestamp", }). + Nillable(). Immutable(), } } diff --git a/internal/api/dto/cart.go b/internal/api/dto/cart.go new file mode 100644 index 0000000..cfc3834 --- /dev/null +++ b/internal/api/dto/cart.go @@ -0,0 +1,123 @@ +package dto + +import ( + "context" + "time" + + domainCart "github.com/omkar273/codegeeky/internal/domain/cart" + ierr "github.com/omkar273/codegeeky/internal/errors" + "github.com/omkar273/codegeeky/internal/types" + "github.com/omkar273/codegeeky/internal/validator" + "github.com/shopspring/decimal" +) + +type CreateCartRequest struct { + Type types.CartType `json:"type" validate:"required"` + ExpiresAt *time.Time `json:"expires_at" validate:"omitempty"` + Metadata map[string]string `json:"metadata" validate:"omitempty"` + LineItems []*CreateCartLineItemRequest `json:"line_items" validate:"omitempty"` +} + +func (c *CreateCartRequest) Validate() error { + + if err := validator.ValidateRequest(c); err != nil { + return err + } + + if c.ExpiresAt != nil { + if c.ExpiresAt.Before(time.Now()) { + return ierr.NewError("expires_at must be in the future"). + WithHint("expires_at must be in the future"). + Mark(ierr.ErrValidation) + } + } + + if len(c.LineItems) > 0 { + for _, lineItem := range c.LineItems { + if err := lineItem.Validate(); err != nil { + return err + } + } + } + return nil +} + +func (c *CreateCartRequest) ToCart(ctx context.Context) *domainCart.Cart { + + lineItems := make([]*domainCart.CartLineItem, 0) + if len(c.LineItems) > 0 { + for _, lineItem := range c.LineItems { + lineItems = append(lineItems, lineItem.ToCartLineItem(ctx)) + } + } + + return &domainCart.Cart{ + ID: types.GenerateUUIDWithPrefix(types.UUID_PREFIX_CART), + UserID: types.GetUserID(ctx), + Type: c.Type, + Subtotal: decimal.Zero, + DiscountAmount: decimal.Zero, + TaxAmount: decimal.Zero, + Total: decimal.Zero, + ExpiresAt: c.ExpiresAt, + Metadata: c.Metadata, + LineItems: lineItems, + BaseModel: types.GetDefaultBaseModel(ctx), + } +} + +type UpdateCartRequest struct { + ID string `json:"id,omitempty"` + ExpiresAt time.Time `json:"expires_at,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + LineItems []*CreateCartLineItemRequest `json:"line_items,omitempty"` +} + +type CreateCartLineItemRequest struct { + CartID string `json:"cart_id" validate:"omitempty"` + EntityID string `json:"entity_id" validate:"required"` + EntityType types.CartLineItemEntityType `json:"entity_type" validate:"required"` + Quantity int `json:"quantity" validate:"required,min=1"` + Metadata map[string]string `json:"metadata" validate:"omitempty"` +} + +func (c *CreateCartLineItemRequest) Validate() error { + if err := validator.ValidateRequest(c); err != nil { + return err + } + + if c.Quantity <= 0 { + return ierr.NewError("quantity must be greater than 0"). + WithHint("quantity must be greater than 0"). + Mark(ierr.ErrValidation) + } + + if c.EntityID == "" { + return ierr.NewError("entity_id is required"). + WithHint("entity_id is required"). + Mark(ierr.ErrValidation) + } + + if err := c.EntityType.Validate(); err != nil { + return err + } + + return nil +} + +func (c *CreateCartLineItemRequest) ToCartLineItem(ctx context.Context) *domainCart.CartLineItem { + return &domainCart.CartLineItem{ + ID: types.GenerateUUIDWithPrefix(types.UUID_PREFIX_CART_LINE_ITEM), + CartID: c.CartID, + EntityID: c.EntityID, + EntityType: c.EntityType, + Quantity: c.Quantity, + PerUnitPrice: decimal.Zero, + TaxAmount: decimal.Zero, + DiscountAmount: decimal.Zero, + Subtotal: decimal.Zero, + Total: decimal.Zero, + Metadata: c.Metadata, + BaseModel: types.GetDefaultBaseModel(ctx), + } +} diff --git a/internal/api/v1/cart.go b/internal/api/v1/cart.go new file mode 100644 index 0000000..b7b1f99 --- /dev/null +++ b/internal/api/v1/cart.go @@ -0,0 +1 @@ +package v1 diff --git a/internal/domain/cart/model.go b/internal/domain/cart/model.go index 75b70af..35b8923 100644 --- a/internal/domain/cart/model.go +++ b/internal/domain/cart/model.go @@ -17,7 +17,7 @@ type Cart struct { DiscountAmount decimal.Decimal `json:"discount_amount,omitempty"` TaxAmount decimal.Decimal `json:"tax_amount,omitempty"` Total decimal.Decimal `json:"total,omitempty"` - ExpiresAt time.Time `json:"expires_at,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` LineItems []*CartLineItem `json:"line_items,omitempty"` types.BaseModel diff --git a/internal/domain/cart/repository.go b/internal/domain/cart/repository.go index 40cdd6e..5578ccf 100644 --- a/internal/domain/cart/repository.go +++ b/internal/domain/cart/repository.go @@ -16,6 +16,9 @@ type Repository interface { List(ctx context.Context, filter *types.CartFilter) ([]*Cart, error) ListAll(ctx context.Context, filter *types.CartFilter) ([]*Cart, error) + // user carts + GetUserDefaultCart(ctx context.Context, userID string) (*Cart, error) + // cart line items CreateCartLineItem(ctx context.Context, cartLineItem *CartLineItem) error GetCartLineItem(ctx context.Context, id string) (*CartLineItem, error) diff --git a/internal/repository/ent/cart.go b/internal/repository/ent/cart.go index b96e743..4e6b5f0 100644 --- a/internal/repository/ent/cart.go +++ b/internal/repository/ent/cart.go @@ -12,6 +12,7 @@ import ( "github.com/omkar273/codegeeky/internal/logger" "github.com/omkar273/codegeeky/internal/postgres" "github.com/omkar273/codegeeky/internal/types" + "github.com/samber/lo" ) type cartRepository struct { @@ -45,7 +46,7 @@ func (r *cartRepository) Create(ctx context.Context, cartData *domainCart.Cart) SetDiscountAmount(cartData.DiscountAmount). SetTaxAmount(cartData.TaxAmount). SetTotal(cartData.Total). - SetExpiresAt(cartData.ExpiresAt). + SetExpiresAt(lo.FromPtr(cartData.ExpiresAt)). SetMetadata(cartData.Metadata). SetStatus(string(types.StatusPublished)). SetCreatedAt(cartData.CreatedAt). @@ -94,7 +95,7 @@ func (r *cartRepository) CreateWithLineItems(ctx context.Context, cartData *doma SetDiscountAmount(cartData.DiscountAmount). SetTaxAmount(cartData.TaxAmount). SetTotal(cartData.Total). - SetExpiresAt(cartData.ExpiresAt). + SetExpiresAt(lo.FromPtr(cartData.ExpiresAt)). SetMetadata(cartData.Metadata). SetStatus(string(types.StatusPublished)). SetCreatedAt(cartData.CreatedAt). @@ -161,7 +162,10 @@ func (r *cartRepository) Get(ctx context.Context, id string) (*domainCart.Cart, r.logger.Debugw("getting cart", "cart_id", id) entCart, err := client.Cart.Query(). - Where(cart.ID(id)). + Where( + cart.ID(id), + cart.UserID(types.GetUserID(ctx)), + ). WithLineItems(). Only(ctx) @@ -195,6 +199,7 @@ func (r *cartRepository) Update(ctx context.Context, cartData *domainCart.Cart) ) _, err := client.Cart.UpdateOneID(cartData.ID). + Where(cart.UserID(types.GetUserID(ctx))). SetMetadata(cartData.Metadata). SetStatus(string(cartData.Status)). SetUpdatedAt(time.Now().UTC()). @@ -240,6 +245,7 @@ func (r *cartRepository) Delete(ctx context.Context, id string) error { // Then delete cart _, err = client.Cart.UpdateOneID(id). + Where(cart.UserID(types.GetUserID(ctx))). SetStatus(string(types.StatusDeleted)). SetUpdatedAt(time.Now().UTC()). SetUpdatedBy(types.GetUserID(ctx)). @@ -298,6 +304,7 @@ func (r *cartRepository) List(ctx context.Context, filter *types.CartFilter) ([] query = r.queryOpts.ApplyEntityQueryOptions(ctx, filter, query) carts, err := query. + Where(cart.UserID(types.GetUserID(ctx))). WithLineItems(). All(ctx) if err != nil { @@ -415,6 +422,7 @@ func (r *cartRepository) UpdateCartLineItem(ctx context.Context, cartLineItem *d ) _, err := client.CartLineItems.UpdateOneID(cartLineItem.ID). + Where(cartlineitems.CartID(cartLineItem.CartID)). SetQuantity(cartLineItem.Quantity). SetPerUnitPrice(cartLineItem.PerUnitPrice). SetTaxAmount(cartLineItem.TaxAmount). @@ -453,6 +461,9 @@ func (r *cartRepository) DeleteCartLineItem(ctx context.Context, id string) erro r.logger.Debugw("deleting cart line item", "line_item_id", id) _, err := client.CartLineItems.UpdateOneID(id). + Where( + cartlineitems.CartID(id), + ). SetStatus(string(types.StatusDeleted)). SetUpdatedAt(time.Now().UTC()). SetUpdatedBy(types.GetUserID(ctx)). @@ -503,6 +514,32 @@ func (r *cartRepository) ListCartLineItems(ctx context.Context, cartId string) ( return cartLineItem.FromEntList(cartLineItems), nil } +func (r *cartRepository) GetUserDefaultCart(ctx context.Context, userID string) (*domainCart.Cart, error) { + client := r.client.Querier(ctx) + + r.logger.Debugw("getting user default cart", "user_id", userID) + + entCart, err := client.Cart.Query(). + Where( + cart.UserID(userID), + cart.Type(string(types.CartTypeDefault)), + cart.Status(string(types.StatusPublished)), + ). + Only(ctx) + + if err != nil { + return nil, ierr.WithError(err). + WithHint("Failed to get user default cart"). + WithReportableDetails(map[string]any{ + "user_id": userID, + }). + Mark(ierr.ErrDatabase) + } + + cartData := &domainCart.Cart{} + return cartData.FromEnt(entCart), nil +} + // CartQuery type alias for better readability type CartQuery = *ent.CartQuery diff --git a/internal/repository/factory.go b/internal/repository/factory.go index fb6b330..415abe4 100644 --- a/internal/repository/factory.go +++ b/internal/repository/factory.go @@ -2,6 +2,7 @@ package repository import ( "github.com/omkar273/codegeeky/internal/config" + "github.com/omkar273/codegeeky/internal/domain/cart" "github.com/omkar273/codegeeky/internal/domain/discount" "github.com/omkar273/codegeeky/internal/domain/internship" "github.com/omkar273/codegeeky/internal/domain/internshipenrollment" @@ -60,3 +61,7 @@ func NewInternshipEnrollmentRepository(params RepositoryParams) internshipenroll func NewInternshipBatchRepository(params RepositoryParams) internship.InternshipBatchRepository { return ent.NewInternshipBatchRepository(params.Client, params.Logger) } + +func NewCartRepository(params RepositoryParams) cart.Repository { + return ent.NewCartRepository(params.Client, params.Logger) +} diff --git a/internal/service/cart.go b/internal/service/cart.go index 6d43c33..2ef054c 100644 --- a/internal/service/cart.go +++ b/internal/service/cart.go @@ -1 +1,466 @@ package service + +import ( + "context" + "time" + + "github.com/omkar273/codegeeky/internal/api/dto" + domainCart "github.com/omkar273/codegeeky/internal/domain/cart" + ierr "github.com/omkar273/codegeeky/internal/errors" + "github.com/omkar273/codegeeky/internal/types" + "github.com/samber/lo" + "github.com/shopspring/decimal" +) + +type CartService interface { + CreateCart(ctx context.Context, req *dto.CreateCartRequest) (*domainCart.Cart, error) + GetCart(ctx context.Context, id string) (*domainCart.Cart, error) + UpdateCart(ctx context.Context, id string, req *dto.UpdateCartRequest) (*domainCart.Cart, error) + DeleteCart(ctx context.Context, id string) error + ListCarts(ctx context.Context, filter *types.CartFilter) ([]*domainCart.Cart, error) + GetCartLineItems(ctx context.Context, cartId string) ([]*domainCart.CartLineItem, error) + + // line item service + AddLineItem(ctx context.Context, req *dto.CreateCartLineItemRequest) (*domainCart.CartLineItem, error) + RemoveLineItem(ctx context.Context, id string) error + GetLineItem(ctx context.Context, id string) (*domainCart.CartLineItem, error) + ListLineItems(ctx context.Context, cartId string) ([]*domainCart.CartLineItem, error) +} + +type cartService struct { + ServiceParams +} + +func NewCartService(params ServiceParams) CartService { + return &cartService{ + ServiceParams: params, + } +} + +func (s *cartService) CreateCart(ctx context.Context, req *dto.CreateCartRequest) (*domainCart.Cart, error) { + if err := req.Validate(); err != nil { + return nil, err + } + + cart := req.ToCart(ctx) + + // if cart type is default, then we need to check if the user has a cart + if req.Type == types.CartTypeDefault { + cart, err := s.CartRepo.GetUserDefaultCart(ctx, cart.UserID) + if err == nil && cart != nil { + return nil, ierr.NewError("user already has a default cart"). + WithHint("user already has a default cart"). + WithReportableDetails(map[string]any{ + "cart_id": cart.ID, + }). + Mark(ierr.ErrValidation) + } + } + + // resolve the line items + lineItems := make([]*domainCart.CartLineItem, 0) + for _, lineItemReq := range req.LineItems { + if lineItemReq.EntityType == types.CartLineItemEntityTypeInternshipBatch { + + internshipBatch, err := s.ServiceParams.InternshipBatchRepo.Get(ctx, lineItemReq.EntityID) + if err != nil { + return nil, err + } + + internship, err := s.ServiceParams.InternshipRepo.Get(ctx, internshipBatch.InternshipID) + if err != nil { + return nil, err + } + + if internship.Status != types.StatusPublished { + return nil, ierr.NewError("internship is not published"). + WithHint("internship is not published"). + WithReportableDetails(map[string]any{ + "internship_id": internship.ID, + }). + Mark(ierr.ErrValidation) + } + + lineItem := lineItemReq.ToCartLineItem(ctx) + + lineItem.EntityID = internship.ID + lineItem.EntityType = types.CartLineItemEntityTypeInternshipBatch + lineItem.CartID = cart.ID + + // PerUnitPrice should be the original/base price (internship.Price) + // This shows customers the "regular price" before discounts + lineItem.PerUnitPrice = internship.Price + + // Calculate subtotal: original price * quantity + lineItem.Subtotal = internship.Price.Mul(decimal.NewFromInt(int64(lineItemReq.Quantity))) + + // Calculate discount amount: (original price - final price) * quantity + // This reflects the internship's built-in discount + discountPerUnit := internship.Price.Sub(internship.Total) + lineItem.DiscountAmount = discountPerUnit.Mul(decimal.NewFromInt(int64(lineItemReq.Quantity))) + + // For now, tax amount is zero since it's not implemented in the internship model + // In a real system, you'd calculate tax based on the final price + lineItem.TaxAmount = decimal.Zero + + // Calculate total: subtotal - discount + tax (should equal internship.Total * quantity) + lineItem.Total = lineItem.Subtotal.Sub(lineItem.DiscountAmount).Add(lineItem.TaxAmount) + + lineItems = append(lineItems, lineItem) + } + } + + // update + cart.LineItems = lineItems + + err := s.ServiceParams.CartRepo.CreateWithLineItems(ctx, cart) + if err != nil { + return nil, err + } + + return cart, nil +} + +// GetCart retrieves a cart by its ID +func (s *cartService) GetCart(ctx context.Context, id string) (*domainCart.Cart, error) { + if id == "" { + return nil, ierr.NewError("cart ID is required"). + WithHint("Please provide a valid cart ID"). + Mark(ierr.ErrValidation) + } + + cart, err := s.ServiceParams.CartRepo.Get(ctx, id) + if err != nil { + return nil, err + } + // Here we are not checking if the user has permission to access this cart + // because userId check is already enforced in the repository + + return cart, nil +} + +// UpdateCart updates an existing cart +func (s *cartService) UpdateCart(ctx context.Context, id string, req *dto.UpdateCartRequest) (*domainCart.Cart, error) { + if id == "" { + return nil, ierr.NewError("cart ID is required"). + WithHint("Please provide a valid cart ID"). + Mark(ierr.ErrValidation) + } + + // Get existing cart + existingCart, err := s.ServiceParams.CartRepo.Get(ctx, id) + if err != nil { + return nil, err + } + + // Update fields if provided + if !req.ExpiresAt.IsZero() { + existingCart.ExpiresAt = lo.ToPtr(req.ExpiresAt) + } + + if req.Metadata != nil { + existingCart.Metadata = req.Metadata + } + + // Update cart + err = s.ServiceParams.CartRepo.Update(ctx, existingCart) + if err != nil { + return nil, err + } + + return existingCart, nil +} + +// DeleteCart deletes a cart by its ID +func (s *cartService) DeleteCart(ctx context.Context, id string) error { + if id == "" { + return ierr.NewError("cart ID is required"). + WithHint("Please provide a valid cart ID"). + Mark(ierr.ErrValidation) + } + + // Get existing cart to check permissions + existingCart, err := s.ServiceParams.CartRepo.Get(ctx, id) + if err != nil { + return err + } + + // Check if user has permission to delete this cart + userID := types.GetUserID(ctx) + if existingCart.UserID != userID { + return ierr.NewError("access denied"). + WithHint("You can only delete your own carts"). + WithReportableDetails(map[string]any{ + "cart_id": id, + "user_id": userID, + }). + Mark(ierr.ErrPermissionDenied) + } + + return s.CartRepo.Delete(ctx, id) +} + +// ListCarts retrieves a list of carts based on filter criteria +func (s *cartService) ListCarts(ctx context.Context, filter *types.CartFilter) ([]*domainCart.Cart, error) { + if filter == nil { + filter = types.NewCartFilter() + } + + filter.UserID = types.GetUserID(ctx) + + // Validate filter + if err := filter.Validate(); err != nil { + return nil, err + } + + return s.CartRepo.List(ctx, filter) +} + +// GetCartLineItems retrieves all line items for a specific cart +func (s *cartService) GetCartLineItems(ctx context.Context, cartId string) ([]*domainCart.CartLineItem, error) { + if cartId == "" { + return nil, ierr.NewError("cart ID is required"). + WithHint("Please provide a valid cart ID"). + Mark(ierr.ErrValidation) + } + + // Verify cart exists and user has access + _, err := s.CartRepo.Get(ctx, cartId) + if err != nil { + return nil, err + } + + return s.CartRepo.ListCartLineItems(ctx, cartId) +} + +// AddLineItem adds a new line item to a cart +func (s *cartService) AddLineItem(ctx context.Context, req *dto.CreateCartLineItemRequest) (*domainCart.CartLineItem, error) { + if err := req.Validate(); err != nil { + return nil, err + } + + var cart *domainCart.Cart + var err error + // TODO: This is a temporary solution to find the user's default cart + // We need to find a better way to handle this + // If cart ID is not provided, try to find user's default cart + if req.CartID == "" { + userID := types.GetUserID(ctx) + cart, err = s.CartRepo.GetUserDefaultCart(ctx, userID) + if err != nil { + return nil, err + } + + if cart == nil { + return nil, ierr.NewError("no default cart found"). + WithHint("Please create a cart first or specify a cart ID"). + Mark(ierr.ErrValidation) + } + + req.CartID = cart.ID + } else { + cart, err = s.CartRepo.Get(ctx, req.CartID) + if err != nil { + return nil, err + } + } + + // Check if user has permission to modify this cart + userID := types.GetUserID(ctx) + if cart.UserID != userID { + return nil, ierr.NewError("access denied"). + WithHint("You can only add items to your own carts"). + WithReportableDetails(map[string]any{ + "cart_id": req.CartID, + "user_id": userID, + }). + Mark(ierr.ErrPermissionDenied) + } + + // Validate entity exists and is available + if req.EntityType == types.CartLineItemEntityTypeInternshipBatch { + internship, err := s.InternshipRepo.Get(ctx, req.EntityID) + if err != nil { + return nil, err + } + + if internship == nil { + return nil, ierr.NewError("internship not found"). + WithHint("The specified internship does not exist"). + WithReportableDetails(map[string]any{ + "internship_id": req.EntityID, + }). + Mark(ierr.ErrValidation) + } + + if internship.Status != types.StatusPublished { + return nil, ierr.NewError("internship is not available"). + WithHint("The specified internship is not currently available for enrollment"). + WithReportableDetails(map[string]any{ + "internship_id": internship.ID, + "status": internship.Status, + }). + Mark(ierr.ErrValidation) + } + } + + // Create line item + lineItem := req.ToCartLineItem(ctx) + lineItem.CartID = req.CartID + + // Set pricing based on entity type + if req.EntityType == types.CartLineItemEntityTypeInternshipBatch { + internship, _ := s.InternshipRepo.Get(ctx, req.EntityID) + lineItem.PerUnitPrice = internship.Price + lineItem.Subtotal = internship.Price.Mul(decimal.NewFromInt(int64(req.Quantity))) + + // Calculate discount amount: (original price - final price) * quantity + discountPerUnit := internship.Price.Sub(internship.Total) + lineItem.DiscountAmount = discountPerUnit.Mul(decimal.NewFromInt(int64(req.Quantity))) + + // For now, tax amount is zero + lineItem.TaxAmount = decimal.Zero + + // Calculate total: subtotal - discount + tax + lineItem.Total = lineItem.Subtotal.Sub(lineItem.DiscountAmount).Add(lineItem.TaxAmount) + } + + // Create the line item + err = s.CartRepo.CreateCartLineItem(ctx, lineItem) + if err != nil { + return nil, err + } + + // Update cart totals + err = s.updateCartTotals(ctx, req.CartID) + if err != nil { + s.Logger.Errorw("failed to update cart totals after adding line item", + "cart_id", req.CartID, "error", err) + // Don't fail the operation for total update failure + } + + return lineItem, nil +} + +// RemoveLineItem removes a line item from a cart +func (s *cartService) RemoveLineItem(ctx context.Context, id string) error { + if id == "" { + return ierr.NewError("line item ID is required"). + WithHint("Please provide a valid line item ID"). + Mark(ierr.ErrValidation) + } + + // Get line item to check permissions + lineItem, err := s.CartRepo.GetCartLineItem(ctx, id) + if err != nil { + return err + } + + // Verify cart exists and user has access + cart, err := s.CartRepo.Get(ctx, lineItem.CartID) + if err != nil { + return err + } + + // Check if user has permission to modify this cart + userID := types.GetUserID(ctx) + if cart.UserID != userID { + return ierr.NewError("access denied"). + WithHint("You can only remove items from your own carts"). + WithReportableDetails(map[string]any{ + "cart_id": lineItem.CartID, + "line_item_id": id, + "user_id": userID, + }). + Mark(ierr.ErrPermissionDenied) + } + + // Delete the line item + err = s.CartRepo.DeleteCartLineItem(ctx, id) + if err != nil { + return err + } + + // Update cart totals + err = s.updateCartTotals(ctx, lineItem.CartID) + if err != nil { + s.Logger.Errorw("failed to update cart totals after removing line item", + "cart_id", lineItem.CartID, "error", err) + // Don't fail the operation for total update failure + } + + return nil +} + +// GetLineItem retrieves a specific line item by its ID +func (s *cartService) GetLineItem(ctx context.Context, id string) (*domainCart.CartLineItem, error) { + if id == "" { + return nil, ierr.NewError("line item ID is required"). + WithHint("Please provide a valid line item ID"). + Mark(ierr.ErrValidation) + } + + lineItem, err := s.CartRepo.GetCartLineItem(ctx, id) + if err != nil { + return nil, err + } + + // Verify cart exists and user has access + cart, err := s.CartRepo.Get(ctx, lineItem.CartID) + if err != nil { + return nil, err + } + + // Check if user has permission to access this cart + userID := types.GetUserID(ctx) + if cart.UserID != userID { + return nil, ierr.NewError("access denied"). + WithHint("You can only access line items from your own carts"). + WithReportableDetails(map[string]any{ + "cart_id": lineItem.CartID, + "line_item_id": id, + "user_id": userID, + }). + Mark(ierr.ErrPermissionDenied) + } + + return lineItem, nil +} + +// ListLineItems retrieves all line items for a specific cart +func (s *cartService) ListLineItems(ctx context.Context, cartId string) ([]*domainCart.CartLineItem, error) { + return s.GetCartLineItems(ctx, cartId) +} + +// updateCartTotals recalculates and updates the cart totals based on line items +func (s *cartService) updateCartTotals(ctx context.Context, cartID string) error { + // Get all line items for the cart + lineItems, err := s.CartRepo.ListCartLineItems(ctx, cartID) + if err != nil { + return err + } + + // Calculate totals + var subtotal, discountAmount, taxAmount, total decimal.Decimal + + for _, item := range lineItems { + subtotal = subtotal.Add(item.Subtotal) + discountAmount = discountAmount.Add(item.DiscountAmount) + taxAmount = taxAmount.Add(item.TaxAmount) + total = total.Add(item.Total) + } + + // Get cart and update totals + cart, err := s.CartRepo.Get(ctx, cartID) + if err != nil { + return err + } + + cart.Subtotal = subtotal + cart.DiscountAmount = discountAmount + cart.TaxAmount = taxAmount + cart.Total = total + cart.UpdatedAt = time.Now().UTC() + + return s.CartRepo.Update(ctx, cart) +} diff --git a/internal/service/factory.go b/internal/service/factory.go index 6b0dafc..4cd2a84 100644 --- a/internal/service/factory.go +++ b/internal/service/factory.go @@ -2,6 +2,7 @@ package service import ( "github.com/omkar273/codegeeky/internal/config" + "github.com/omkar273/codegeeky/internal/domain/cart" "github.com/omkar273/codegeeky/internal/domain/discount" "github.com/omkar273/codegeeky/internal/domain/internship" "github.com/omkar273/codegeeky/internal/domain/internshipenrollment" @@ -30,7 +31,7 @@ type ServiceParams struct { InternshipBatchRepo internship.InternshipBatchRepository CategoryRepo internship.CategoryRepository InternshipEnrollmentRepo internshipenrollment.Repository - + CartRepo cart.Repository // Service dependencies WebhookPublisher publisher.WebhookPublisher diff --git a/internal/testutil/inmemory_cart_store.go b/internal/testutil/inmemory_cart_store.go index e9314c3..262a0ed 100644 --- a/internal/testutil/inmemory_cart_store.go +++ b/internal/testutil/inmemory_cart_store.go @@ -379,6 +379,42 @@ func (s *InMemoryCartStore) ListCartLineItems(ctx context.Context, cartId string return items, nil } +func (s *InMemoryCartStore) GetUserDefaultCart(ctx context.Context, userID string) (*cart.Cart, error) { + // Get all carts for the user + allCarts, err := s.InMemoryStore.List(ctx, nil, nil, nil) + if err != nil { + return nil, ierr.WithError(err). + WithHint("Failed to get user default cart"). + WithReportableDetails(map[string]any{ + "user_id": userID, + }). + Mark(ierr.ErrDatabase) + } + + // Filter for the user's default cart + for _, c := range allCarts { + if c.UserID == userID && + c.Type == types.CartTypeDefault && + c.Status == types.StatusPublished { + // Load line items for the cart + lineItems, err := s.ListCartLineItems(ctx, c.ID) + if err != nil { + return nil, err + } + c.LineItems = lineItems + return c, nil + } + } + + // No default cart found + return nil, ierr.NewError("user default cart not found"). + WithHint("No default cart exists for this user"). + WithReportableDetails(map[string]any{ + "user_id": userID, + }). + Mark(ierr.ErrNotFound) +} + // Clear clears both cart and line item stores func (s *InMemoryCartStore) Clear() { s.InMemoryStore.Clear() diff --git a/internal/types/cart.go b/internal/types/cart.go index d7acb5d..5f8a970 100644 --- a/internal/types/cart.go +++ b/internal/types/cart.go @@ -36,8 +36,8 @@ func (c CartType) Validate() error { type CartLineItemEntityType string const ( - CartLineItemEntityTypeInternship CartLineItemEntityType = "internship" - CartLineItemEntityTypeCourse CartLineItemEntityType = "course" + CartLineItemEntityTypeInternshipBatch CartLineItemEntityType = "internship_batch" + CartLineItemEntityTypeCourse CartLineItemEntityType = "course" ) func (c CartLineItemEntityType) String() string { @@ -45,7 +45,7 @@ func (c CartLineItemEntityType) String() string { } func (c CartLineItemEntityType) Validate() error { - allowedValues := []CartLineItemEntityType{CartLineItemEntityTypeInternship, CartLineItemEntityTypeCourse} + allowedValues := []CartLineItemEntityType{CartLineItemEntityTypeInternshipBatch, CartLineItemEntityTypeCourse} if !slices.Contains(allowedValues, c) { return ierr.NewError("INVALID_CART_LINE_ITEM_ENTITY_TYPE"). From 5bb0f3ccef77dee1e0835c0278fa3b65506c3bfa Mon Sep 17 00:00:00 2001 From: omkar sonawane Date: Sun, 6 Jul 2025 19:19:35 +0530 Subject: [PATCH 04/10] Enhance cart service and API with new response structures --- internal/api/dto/cart.go | 12 ++++++++ internal/api/v1/cart.go | 41 +++++++++++++++++++++++++ internal/service/cart.go | 65 ++++++++++++++++++++++++++++------------ 3 files changed, 99 insertions(+), 19 deletions(-) diff --git a/internal/api/dto/cart.go b/internal/api/dto/cart.go index cfc3834..fcdef53 100644 --- a/internal/api/dto/cart.go +++ b/internal/api/dto/cart.go @@ -121,3 +121,15 @@ func (c *CreateCartLineItemRequest) ToCartLineItem(ctx context.Context) *domainC BaseModel: types.GetDefaultBaseModel(ctx), } } + +type CartResponse struct { + *domainCart.Cart +} + +func (c *CartResponse) FromDomain(cart *domainCart.Cart) *CartResponse { + return &CartResponse{ + Cart: cart, + } +} + +type ListCartResponse = types.ListResponse[*CartResponse] diff --git a/internal/api/v1/cart.go b/internal/api/v1/cart.go index b7b1f99..f2108e8 100644 --- a/internal/api/v1/cart.go +++ b/internal/api/v1/cart.go @@ -1 +1,42 @@ package v1 + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/omkar273/codegeeky/internal/logger" + "github.com/omkar273/codegeeky/internal/service" +) + +type CartHandler struct { + cartService service.CartService + logger *logger.Logger +} + +func NewCartHandler(cartService service.CartService, logger *logger.Logger) *CartHandler { + return &CartHandler{cartService: cartService, logger: logger} +} + +// @Summary Get a cart +// @Description Get a cart by ID +// @Tags Cart +// @Accept json +// @Produce json +// @Param id path string true "Cart ID" +// @Success 200 {object} dto.CartResponse +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts/{id} [get] +// @Security ApiKeyAuth +func (h *CartHandler) GetCart(c *gin.Context) { + id := c.Param("id") + + cart, err := h.cartService.GetCart(c.Request.Context(), id) + if err != nil { + h.logger.Error("failed to get cart", "error", err) + c.Error(err) + return + } + + c.JSON(http.StatusOK, cart) +} diff --git a/internal/service/cart.go b/internal/service/cart.go index 2ef054c..df01a15 100644 --- a/internal/service/cart.go +++ b/internal/service/cart.go @@ -13,11 +13,11 @@ import ( ) type CartService interface { - CreateCart(ctx context.Context, req *dto.CreateCartRequest) (*domainCart.Cart, error) - GetCart(ctx context.Context, id string) (*domainCart.Cart, error) - UpdateCart(ctx context.Context, id string, req *dto.UpdateCartRequest) (*domainCart.Cart, error) + CreateCart(ctx context.Context, req *dto.CreateCartRequest) (*dto.CartResponse, error) + GetCart(ctx context.Context, id string) (*dto.CartResponse, error) + UpdateCart(ctx context.Context, id string, req *dto.UpdateCartRequest) (*dto.CartResponse, error) DeleteCart(ctx context.Context, id string) error - ListCarts(ctx context.Context, filter *types.CartFilter) ([]*domainCart.Cart, error) + ListCarts(ctx context.Context, filter *types.CartFilter) (*dto.ListCartResponse, error) GetCartLineItems(ctx context.Context, cartId string) ([]*domainCart.CartLineItem, error) // line item service @@ -37,7 +37,7 @@ func NewCartService(params ServiceParams) CartService { } } -func (s *cartService) CreateCart(ctx context.Context, req *dto.CreateCartRequest) (*domainCart.Cart, error) { +func (s *cartService) CreateCart(ctx context.Context, req *dto.CreateCartRequest) (*dto.CartResponse, error) { if err := req.Validate(); err != nil { return nil, err } @@ -62,12 +62,12 @@ func (s *cartService) CreateCart(ctx context.Context, req *dto.CreateCartRequest for _, lineItemReq := range req.LineItems { if lineItemReq.EntityType == types.CartLineItemEntityTypeInternshipBatch { - internshipBatch, err := s.ServiceParams.InternshipBatchRepo.Get(ctx, lineItemReq.EntityID) + internshipBatch, err := s.InternshipBatchRepo.Get(ctx, lineItemReq.EntityID) if err != nil { return nil, err } - internship, err := s.ServiceParams.InternshipRepo.Get(ctx, internshipBatch.InternshipID) + internship, err := s.InternshipRepo.Get(ctx, internshipBatch.InternshipID) if err != nil { return nil, err } @@ -113,34 +113,38 @@ func (s *cartService) CreateCart(ctx context.Context, req *dto.CreateCartRequest // update cart.LineItems = lineItems - err := s.ServiceParams.CartRepo.CreateWithLineItems(ctx, cart) + err := s.CartRepo.CreateWithLineItems(ctx, cart) if err != nil { return nil, err } - return cart, nil + return &dto.CartResponse{ + Cart: cart, + }, nil } // GetCart retrieves a cart by its ID -func (s *cartService) GetCart(ctx context.Context, id string) (*domainCart.Cart, error) { +func (s *cartService) GetCart(ctx context.Context, id string) (*dto.CartResponse, error) { if id == "" { return nil, ierr.NewError("cart ID is required"). WithHint("Please provide a valid cart ID"). Mark(ierr.ErrValidation) } - cart, err := s.ServiceParams.CartRepo.Get(ctx, id) + cart, err := s.CartRepo.Get(ctx, id) if err != nil { return nil, err } // Here we are not checking if the user has permission to access this cart // because userId check is already enforced in the repository - return cart, nil + return &dto.CartResponse{ + Cart: cart, + }, nil } // UpdateCart updates an existing cart -func (s *cartService) UpdateCart(ctx context.Context, id string, req *dto.UpdateCartRequest) (*domainCart.Cart, error) { +func (s *cartService) UpdateCart(ctx context.Context, id string, req *dto.UpdateCartRequest) (*dto.CartResponse, error) { if id == "" { return nil, ierr.NewError("cart ID is required"). WithHint("Please provide a valid cart ID"). @@ -148,7 +152,7 @@ func (s *cartService) UpdateCart(ctx context.Context, id string, req *dto.Update } // Get existing cart - existingCart, err := s.ServiceParams.CartRepo.Get(ctx, id) + existingCart, err := s.CartRepo.Get(ctx, id) if err != nil { return nil, err } @@ -163,12 +167,14 @@ func (s *cartService) UpdateCart(ctx context.Context, id string, req *dto.Update } // Update cart - err = s.ServiceParams.CartRepo.Update(ctx, existingCart) + err = s.CartRepo.Update(ctx, existingCart) if err != nil { return nil, err } - return existingCart, nil + return &dto.CartResponse{ + Cart: existingCart, + }, nil } // DeleteCart deletes a cart by its ID @@ -180,7 +186,7 @@ func (s *cartService) DeleteCart(ctx context.Context, id string) error { } // Get existing cart to check permissions - existingCart, err := s.ServiceParams.CartRepo.Get(ctx, id) + existingCart, err := s.CartRepo.Get(ctx, id) if err != nil { return err } @@ -201,7 +207,7 @@ func (s *cartService) DeleteCart(ctx context.Context, id string) error { } // ListCarts retrieves a list of carts based on filter criteria -func (s *cartService) ListCarts(ctx context.Context, filter *types.CartFilter) ([]*domainCart.Cart, error) { +func (s *cartService) ListCarts(ctx context.Context, filter *types.CartFilter) (*dto.ListCartResponse, error) { if filter == nil { filter = types.NewCartFilter() } @@ -213,7 +219,28 @@ func (s *cartService) ListCarts(ctx context.Context, filter *types.CartFilter) ( return nil, err } - return s.CartRepo.List(ctx, filter) + count, err := s.CartRepo.Count(ctx, filter) + if err != nil { + return nil, err + } + + carts, err := s.CartRepo.List(ctx, filter) + if err != nil { + return nil, err + } + + // Build response + response := &dto.ListCartResponse{ + Items: make([]*dto.CartResponse, count), + Pagination: types.NewPaginationResponse(count, filter.GetLimit(), filter.GetOffset()), + } + + // Add items to response + for i, cart := range carts { + response.Items[i] = &dto.CartResponse{Cart: cart} + } + + return response, nil } // GetCartLineItems retrieves all line items for a specific cart From 323d700b5e85fa649fa0b4e8a658a198183d0b71 Mon Sep 17 00:00:00 2001 From: omkar sonawane Date: Sun, 6 Jul 2025 20:14:39 +0530 Subject: [PATCH 05/10] Implement development authentication features and update configuration --- .sample.env | 1 + Makefile | 12 + cmd/dev-token/main.go | 73 + docs/swagger/swagger-3-0.json | 2295 +++++++++++++++++++++++++++++- ent/migrate/schema.go | 4 +- ent/schema/category.go | 3 +- go.sum | 8 + internal/config/config.go | 2 + internal/config/config.yaml | 1 + internal/rest/middleware/auth.go | 16 +- scripts/dev-auth-example.sh | 131 ++ 11 files changed, 2540 insertions(+), 6 deletions(-) create mode 100644 cmd/dev-token/main.go create mode 100755 scripts/dev-auth-example.sh diff --git a/.sample.env b/.sample.env index 015c0c1..7a7f145 100644 --- a/.sample.env +++ b/.sample.env @@ -23,6 +23,7 @@ CAYGNUS_SUPABASE_URL= CAYGNUS_SUPABASE_KEY= CAYGNUS_SUPABASE_JWT_SECRET= CAYGNUS_SUPABASE_SERVICE_KEY= +CAYGNUS_SUPABASE_DEV_API_KEY= # Encryption Configuration CAYGNUS_SECRETS_ENCRYPTION_KEY= diff --git a/Makefile b/Makefile index 6766214..029d2c9 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,18 @@ generate-dev-keys: @./scripts/generate-dev-keys.sh @echo "✅ Development key pair generated" +.PHONY: generate-dev-token +generate-dev-token: + @echo "Generating development JWT token..." + @echo "Usage: make generate-dev-token JWT_SECRET=your_jwt_secret" + @if [ -z "$(JWT_SECRET)" ]; then \ + echo "❌ Error: JWT_SECRET is required"; \ + echo "Example: make generate-dev-token JWT_SECRET=your_jwt_secret_here"; \ + exit 1; \ + fi + @go run cmd/dev-token/main.go "$(JWT_SECRET)" + @echo "✅ Development JWT token generated" + .PHONY: install-hooks install-hooks: @echo "Installing Git hooks..." diff --git a/cmd/dev-token/main.go b/cmd/dev-token/main.go new file mode 100644 index 0000000..d92258a --- /dev/null +++ b/cmd/dev-token/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "crypto/rand" + "fmt" + "log" + "os" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +func main() { + if len(os.Args) < 2 { + fmt.Println("Usage: go run cmd/dev-token/main.go ") + fmt.Println("This script generates a long-lived JWT token for development purposes.") + fmt.Println("The token will be valid for 1 year from now.") + os.Exit(1) + } + + jwtSecret := os.Args[1] + + // Generate a random user ID for development + userID := generateRandomID() + + // Create claims for the token + claims := jwt.MapClaims{ + "sub": userID, + "aud": "authenticated", + "role": "authenticated", + "email": "dev@example.com", + "phone": "", + "app_metadata": map[string]interface{}{ + "role": "ADMIN", + }, + "iat": time.Now().Unix(), + "exp": time.Now().AddDate(1, 0, 0).Unix(), // 1 year from now + } + + // Create the token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + // Sign the token + tokenString, err := token.SignedString([]byte(jwtSecret)) + if err != nil { + log.Fatalf("Failed to sign token: %v", err) + } + + fmt.Println("=== Development JWT Token ===") + fmt.Println("This token is valid for 1 year from now.") + fmt.Println("Use it in your Authorization header as: Bearer ") + fmt.Println() + fmt.Println("Token:") + fmt.Println(tokenString) + fmt.Println() + fmt.Println("User ID:", userID) + fmt.Println("Email: dev@example.com") + fmt.Println("Role: ADMIN") + fmt.Println("Expires:", time.Now().AddDate(1, 0, 0).Format("2006-01-02 15:04:05")) + fmt.Println() + fmt.Println("⚠️ WARNING: This token is for development only!") + fmt.Println("⚠️ Never use this in production!") + fmt.Println("⚠️ Keep your JWT secret secure!") +} + +func generateRandomID() string { + bytes := make([]byte, 16) + _, err := rand.Read(bytes) + if err != nil { + log.Fatalf("Failed to generate random ID: %v", err) + } + return fmt.Sprintf("%x-%x-%x-%x-%x", bytes[0:4], bytes[4:6], bytes[6:8], bytes[8:10], bytes[10:16]) +} diff --git a/docs/swagger/swagger-3-0.json b/docs/swagger/swagger-3-0.json index 7fad873..8529bee 100644 --- a/docs/swagger/swagger-3-0.json +++ b/docs/swagger/swagger-3-0.json @@ -1 +1,2294 @@ -{"openapi":"3.0.1","info":{"title":"CodeGeeky API","description":"API for CodeGeeky","termsOfService":"http://example.com/terms/","contact":{"name":"API Support","email":"support@example.com"},"version":"1.0"},"servers":[{"url":"//localhost:8080/v1"}],"paths":{"/auth/signup":{"post":{"tags":["Auth"],"summary":"Signup","description":"Signup","requestBody":{"description":"Signup request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.SignupRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.SignupResponse"}}}}},"x-codegen-request-body-name":"signupRequest"}},"/categories":{"get":{"tags":["Category"],"summary":"List categories","description":"List categories with optional filtering","parameters":[{"name":"category_ids","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"end_time","in":"query","schema":{"type":"string"}},{"name":"expand","in":"query","schema":{"type":"string"}},{"name":"internship_ids","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"limit","in":"query","schema":{"maximum":1000,"minimum":1,"type":"integer"}},{"name":"name","in":"query","description":"These fields are used to filter categories by name","schema":{"type":"string"}},{"name":"offset","in":"query","schema":{"minimum":0,"type":"integer"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"start_time","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ListCategoryResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"post":{"tags":["Category"],"summary":"Create a new category","description":"Create a new category with the provided details","requestBody":{"description":"Category details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateCategoryRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CategoryResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"category"}},"/categories/{id}":{"get":{"tags":["Category"],"summary":"Get a category by ID","description":"Get a category by its unique identifier","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CategoryResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"put":{"tags":["Category"],"summary":"Update a category","description":"Update a category by its unique identifier","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Category details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateCategoryRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CategoryResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"category"},"delete":{"tags":["Category"],"summary":"Delete a category","description":"Delete a category by its unique identifier","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","content":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}}},"/discounts":{"get":{"tags":["Discount"],"summary":"List discounts","description":"List discounts with optional filtering","parameters":[{"name":"discount_type","in":"query","schema":{"type":"string","enum":["flat","percentage"],"x-enum-varnames":["DiscountTypeFlat","DiscountTypePercentage"]},"x-enum-varnames":["DiscountTypeFlat","DiscountTypePercentage"]},{"name":"end_time","in":"query","schema":{"type":"string"}},{"name":"expand","in":"query","schema":{"type":"string"}},{"name":"is_combinable","in":"query","schema":{"type":"boolean"}},{"name":"limit","in":"query","schema":{"maximum":1000,"minimum":1,"type":"integer"}},{"name":"min_order_value","in":"query","schema":{"type":"number"}},{"name":"offset","in":"query","schema":{"minimum":0,"type":"integer"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"start_time","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},{"name":"valid_from","in":"query","schema":{"type":"string"}},{"name":"valid_until","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ListDiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]},"post":{"tags":["Discount"],"summary":"Create a new discount","description":"Create a new discount with the provided details","requestBody":{"description":"Discount details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateDiscountRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}],"x-codegen-request-body-name":"discount"}},"/discounts/code/{code}":{"get":{"tags":["Discount"],"summary":"Get a discount by code","description":"Get a discount by its unique code","parameters":[{"name":"code","in":"path","description":"Discount code","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/discounts/{id}":{"get":{"tags":["Discount"],"summary":"Get a discount by ID","description":"Get a discount by its unique identifier","parameters":[{"name":"id","in":"path","description":"Discount ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]},"put":{"tags":["Discount"],"summary":"Update a discount by ID","description":"Update a discount by its unique identifier","parameters":[{"name":"id","in":"path","description":"Discount ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Discount details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateDiscountRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}],"x-codegen-request-body-name":"discount"},"delete":{"tags":["Discount"],"summary":"Delete a discount by ID","description":"Delete a discount by its unique identifier","parameters":[{"name":"id","in":"path","description":"Discount ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","content":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/internships":{"get":{"tags":["Internship"],"summary":"List internships","description":"List internships with optional filtering","parameters":[{"name":"category_ids","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"duration_in_weeks","in":"query","description":"These fields are used to filter internships by duration in weeks","schema":{"maximum":52,"minimum":1,"type":"integer"}},{"name":"end_time","in":"query","schema":{"type":"string"}},{"name":"expand","in":"query","schema":{"type":"string"}},{"name":"internship_ids","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"levels","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string","enum":["beginner","intermediate","advanced"]}}},{"name":"limit","in":"query","schema":{"maximum":1000,"minimum":1,"type":"integer"}},{"name":"max_price","in":"query","description":"These fields are used to filter internships by price","schema":{"type":"number"}},{"name":"min_price","in":"query","schema":{"type":"number"}},{"name":"modes","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string","enum":["remote","hybrid","onsite"]}}},{"name":"name","in":"query","description":"These fields are used to filter internships by category, level and mode","schema":{"type":"string"}},{"name":"offset","in":"query","schema":{"minimum":0,"type":"integer"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"start_time","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ListInternshipResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"post":{"tags":["Internship"],"summary":"Create a new internship","description":"Create a new internship with the provided details","requestBody":{"description":"Internship details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateInternshipRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.InternshipResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"internship"}},"/internships/{id}":{"get":{"tags":["Internship"],"summary":"Get an internship by ID","description":"Get an internship by its unique identifier","parameters":[{"name":"id","in":"path","description":"Internship ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.InternshipResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"put":{"tags":["Internship"],"summary":"Update an internship","description":"Update an internship by its unique identifier","parameters":[{"name":"id","in":"path","description":"Internship ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Internship details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateInternshipRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.InternshipResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"internship"},"delete":{"tags":["Internship"],"summary":"Delete an internship","description":"Delete an internship by its unique identifier","parameters":[{"name":"id","in":"path","description":"Internship ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","content":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}}},"/user":{"put":{"tags":["User"],"summary":"Update current user","description":"Update the current user's information","requestBody":{"description":"Update user request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateUserRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.MeResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"request"}},"/user/me":{"get":{"tags":["User"],"summary":"Get current user","description":"Get the current user's information","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.MeResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}}}},"components":{"schemas":{"dto.CategoryResponse":{"type":"object","properties":{"created_at":{"type":"string"},"created_by":{"type":"string"},"description":{"type":"string","description":"Description holds the value of the \"description\" field."},"id":{"type":"string","description":"ID of the ent."},"internships":{"type":"array","description":"internships holds the value of the internships edge.","items":{"$ref":"#/components/schemas/internship.Internship"}},"lookup_key":{"type":"string","description":"LookupKey holds the value of the \"lookup_key\" field."},"name":{"type":"string","description":"Name holds the value of the \"name\" field."},"status":{"$ref":"#/components/schemas/types.Status"},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"dto.CreateCategoryRequest":{"required":["lookup_key","name"],"type":"object","properties":{"description":{"type":"string"},"lookup_key":{"type":"string"},"name":{"type":"string"}}},"dto.CreateDiscountRequest":{"required":["code","discount_type","discount_value"],"type":"object","properties":{"code":{"type":"string"},"description":{"type":"string"},"discount_type":{"$ref":"#/components/schemas/types.DiscountType"},"discount_value":{"type":"number"},"is_active":{"type":"boolean"},"is_combinable":{"type":"boolean"},"max_uses":{"type":"integer"},"metadata":{"$ref":"#/components/schemas/types.Metadata"},"min_order_value":{"type":"number"},"valid_from":{"type":"string"},"valid_until":{"type":"string"}}},"dto.CreateInternshipRequest":{"required":["currency","description","level","lookup_key","mode","price","title"],"type":"object","properties":{"benefits":{"type":"array","items":{"type":"string"}},"category_ids":{"type":"array","items":{"type":"string"}},"currency":{"type":"string"},"description":{"minLength":10,"type":"string"},"duration_in_weeks":{"minimum":0,"type":"integer"},"flat_discount":{"type":"number"},"learning_outcomes":{"type":"array","items":{"type":"string"}},"level":{"$ref":"#/components/schemas/types.InternshipLevel"},"lookup_key":{"type":"string"},"mode":{"$ref":"#/components/schemas/types.InternshipMode"},"percentage_discount":{"type":"number"},"prerequisites":{"type":"array","items":{"type":"string"}},"price":{"type":"number"},"skills":{"type":"array","items":{"type":"string"}},"title":{"maxLength":255,"minLength":3,"type":"string"}}},"dto.DiscountResponse":{"type":"object","properties":{"code":{"type":"string"},"created_at":{"type":"string"},"created_by":{"type":"string"},"description":{"type":"string"},"discount_type":{"$ref":"#/components/schemas/types.DiscountType"},"discount_value":{"type":"number"},"id":{"type":"string"},"is_active":{"type":"boolean"},"is_combinable":{"type":"boolean"},"max_uses":{"type":"integer"},"metadata":{"$ref":"#/components/schemas/types.Metadata"},"min_order_value":{"type":"number"},"status":{"$ref":"#/components/schemas/types.Status"},"updated_at":{"type":"string"},"updated_by":{"type":"string"},"valid_from":{"type":"string"},"valid_until":{"type":"string"}}},"dto.InternshipResponse":{"type":"object","properties":{"benefits":{"type":"array","description":"Benefits of the internship","items":{"type":"string"}},"categories":{"type":"array","description":"Categories holds the value of the categories edge.","items":{"$ref":"#/components/schemas/internship.Category"}},"created_at":{"type":"string"},"created_by":{"type":"string"},"currency":{"type":"string","description":"Currency of the internship"},"description":{"type":"string","description":"Description holds the value of the \"description\" field."},"duration_in_weeks":{"type":"integer","description":"Alternative to months for shorter internships"},"flat_discount":{"type":"number","description":"Flat discount on the internship"},"id":{"type":"string","description":"ID of the ent."},"learning_outcomes":{"type":"array","description":"What students will learn in the internship","items":{"type":"string"}},"level":{"$ref":"#/components/schemas/types.InternshipLevel"},"lookup_key":{"type":"string","description":"LookupKey holds the value of the \"lookup_key\" field."},"mode":{"$ref":"#/components/schemas/types.InternshipMode"},"percentage_discount":{"type":"number","description":"Percentage discount on the internship"},"prerequisites":{"type":"array","description":"Prerequisites or recommended knowledge","items":{"type":"string"}},"price":{"type":"number","description":"Price of the internship"},"skills":{"type":"array","description":"List of required skills","items":{"type":"string"}},"status":{"$ref":"#/components/schemas/types.Status"},"title":{"type":"string","description":"Title holds the value of the \"title\" field."},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"dto.ListCategoryResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/dto.CategoryResponse"}},"pagination":{"$ref":"#/components/schemas/types.PaginationResponse"}}},"dto.ListDiscountResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/dto.DiscountResponse"}},"pagination":{"$ref":"#/components/schemas/types.PaginationResponse"}}},"dto.ListInternshipResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/dto.InternshipResponse"}},"pagination":{"$ref":"#/components/schemas/types.PaginationResponse"}}},"dto.MeResponse":{"type":"object","properties":{"email":{"type":"string"},"full_name":{"type":"string"},"id":{"type":"string"},"phone":{"type":"string"},"role":{"type":"string"}}},"dto.SignupRequest":{"required":["access_token","email","full_name","role"],"type":"object","properties":{"access_token":{"type":"string","description":"access token"},"email":{"type":"string","description":"basic info"},"full_name":{"type":"string"},"phone":{"type":"string"},"role":{"$ref":"#/components/schemas/types.UserRole"}}},"dto.SignupResponse":{"type":"object","properties":{"access_token":{"type":"string"},"id":{"type":"string"}}},"dto.UpdateCategoryRequest":{"type":"object","properties":{"description":{"type":"string"},"lookup_key":{"type":"string"},"name":{"type":"string"}}},"dto.UpdateDiscountRequest":{"type":"object","properties":{"description":{"type":"string"},"is_active":{"type":"boolean"},"is_combinable":{"type":"boolean"},"max_uses":{"type":"integer"},"metadata":{"$ref":"#/components/schemas/types.Metadata"},"min_order_value":{"type":"number"},"valid_from":{"type":"string"},"valid_until":{"type":"string"}}},"dto.UpdateInternshipRequest":{"type":"object","properties":{"benefits":{"type":"array","items":{"type":"string"}},"category_ids":{"type":"array","items":{"type":"string"}},"currency":{"type":"string"},"description":{"minLength":10,"type":"string"},"duration_in_weeks":{"minimum":0,"type":"integer"},"flat_discount":{"type":"number"},"learning_outcomes":{"type":"array","items":{"type":"string"}},"level":{"$ref":"#/components/schemas/types.InternshipLevel"},"lookup_key":{"type":"string"},"mode":{"$ref":"#/components/schemas/types.InternshipMode"},"percentage_discount":{"type":"number"},"prerequisites":{"type":"array","items":{"type":"string"}},"price":{"type":"number"},"skills":{"type":"array","items":{"type":"string"}},"title":{"maxLength":255,"minLength":3,"type":"string"}}},"dto.UpdateUserRequest":{"type":"object","properties":{"full_name":{"type":"string"},"phone":{"type":"string"}}},"ierr.ErrorDetail":{"type":"object","properties":{"details":{"type":"object","additionalProperties":{"type":"object"}},"internal_error":{"type":"string"},"message":{"type":"string"}}},"ierr.ErrorResponse":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ierr.ErrorDetail"},"success":{"type":"boolean"}}},"internship.Category":{"type":"object","properties":{"created_at":{"type":"string"},"created_by":{"type":"string"},"description":{"type":"string","description":"Description holds the value of the \"description\" field."},"id":{"type":"string","description":"ID of the ent."},"internships":{"type":"array","description":"internships holds the value of the internships edge.","items":{"$ref":"#/components/schemas/internship.Internship"}},"lookup_key":{"type":"string","description":"LookupKey holds the value of the \"lookup_key\" field."},"name":{"type":"string","description":"Name holds the value of the \"name\" field."},"status":{"$ref":"#/components/schemas/types.Status"},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"internship.Internship":{"type":"object","properties":{"benefits":{"type":"array","description":"Benefits of the internship","items":{"type":"string"}},"categories":{"type":"array","description":"Categories holds the value of the categories edge.","items":{"$ref":"#/components/schemas/internship.Category"}},"created_at":{"type":"string"},"created_by":{"type":"string"},"currency":{"type":"string","description":"Currency of the internship"},"description":{"type":"string","description":"Description holds the value of the \"description\" field."},"duration_in_weeks":{"type":"integer","description":"Alternative to months for shorter internships"},"flat_discount":{"type":"number","description":"Flat discount on the internship"},"id":{"type":"string","description":"ID of the ent."},"learning_outcomes":{"type":"array","description":"What students will learn in the internship","items":{"type":"string"}},"level":{"$ref":"#/components/schemas/types.InternshipLevel"},"lookup_key":{"type":"string","description":"LookupKey holds the value of the \"lookup_key\" field."},"mode":{"$ref":"#/components/schemas/types.InternshipMode"},"percentage_discount":{"type":"number","description":"Percentage discount on the internship"},"prerequisites":{"type":"array","description":"Prerequisites or recommended knowledge","items":{"type":"string"}},"price":{"type":"number","description":"Price of the internship"},"skills":{"type":"array","description":"List of required skills","items":{"type":"string"}},"status":{"$ref":"#/components/schemas/types.Status"},"title":{"type":"string","description":"Title holds the value of the \"title\" field."},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"types.DiscountType":{"type":"string","enum":["flat","percentage"],"x-enum-varnames":["DiscountTypeFlat","DiscountTypePercentage"]},"types.InternshipLevel":{"type":"string","enum":["beginner","intermediate","advanced"],"x-enum-varnames":["InternshipLevelBeginner","InternshipLevelIntermediate","InternshipLevelAdvanced"]},"types.InternshipMode":{"type":"string","enum":["remote","hybrid","onsite"],"x-enum-varnames":["InternshipModeRemote","InternshipModeHybrid","InternshipModeOnsite"]},"types.Metadata":{"type":"object","additionalProperties":{"type":"string"}},"types.PaginationResponse":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"types.Status":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"types.UserRole":{"type":"string","enum":["STUDENT","INSTRUCTOR","ADMIN"],"x-enum-varnames":["UserRoleStudent","UserRoleInstructor","UserRoleAdmin"]}},"securitySchemes":{"Authorization":{"type":"apiKey","description":"Enter the token with the `Bearer ` prefix, e.g. `Bearer `.","name":"Authorization","in":"header"}}},"x-original-swagger-version":"2.0"} \ No newline at end of file +{ + "openapi": "3.0.1", + "info": { + "title": "CodeGeeky API", + "description": "API for CodeGeeky", + "termsOfService": "http://example.com/terms/", + "contact": { + "name": "API Support", + "email": "support@example.com" + }, + "version": "1.0" + }, + "servers": [ + { + "url": "//localhost:8080/v1" + } + ], + "paths": { + "/auth/signup": { + "post": { + "tags": [ + "Auth" + ], + "summary": "Signup", + "description": "Signup", + "requestBody": { + "description": "Signup request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.SignupRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.SignupResponse" + } + } + } + } + }, + "x-codegen-request-body-name": "signupRequest" + } + }, + "/categories": { + "get": { + "tags": [ + "Category" + ], + "summary": "List categories", + "description": "List categories with optional filtering", + "parameters": [ + { + "name": "category_ids", + "in": "query", + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "end_time", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "expand", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "internship_ids", + "in": "query", + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "name": "name", + "in": "query", + "description": "These fields are used to filter categories by name", + "schema": { + "type": "string" + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "minimum": 0, + "type": "integer" + } + }, + { + "name": "order", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "start_time", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "published", + "deleted", + "archived", + "inactive", + "pending" + ], + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ] + }, + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.ListCategoryResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Category" + ], + "summary": "Create a new category", + "description": "Create a new category with the provided details", + "requestBody": { + "description": "Category details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.CreateCategoryRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.CategoryResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "x-codegen-request-body-name": "category" + } + }, + "/categories/{id}": { + "get": { + "tags": [ + "Category" + ], + "summary": "Get a category by ID", + "description": "Get a category by its unique identifier", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Category ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.CategoryResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + } + }, + "put": { + "tags": [ + "Category" + ], + "summary": "Update a category", + "description": "Update a category by its unique identifier", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Category ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Category details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.UpdateCategoryRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.CategoryResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "x-codegen-request-body-name": "category" + }, + "delete": { + "tags": [ + "Category" + ], + "summary": "Delete a category", + "description": "Delete a category by its unique identifier", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Category ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content", + "content": {} + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + } + } + }, + "/discounts": { + "get": { + "tags": [ + "Discount" + ], + "summary": "List discounts", + "description": "List discounts with optional filtering", + "parameters": [ + { + "name": "discount_type", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "flat", + "percentage" + ], + "x-enum-varnames": [ + "DiscountTypeFlat", + "DiscountTypePercentage" + ] + }, + "x-enum-varnames": [ + "DiscountTypeFlat", + "DiscountTypePercentage" + ] + }, + { + "name": "end_time", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "expand", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "is_combinable", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "name": "min_order_value", + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "minimum": 0, + "type": "integer" + } + }, + { + "name": "order", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "start_time", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "published", + "deleted", + "archived", + "inactive", + "pending" + ], + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ] + }, + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ] + }, + { + "name": "valid_from", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "valid_until", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.ListDiscountResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "tags": [ + "Discount" + ], + "summary": "Create a new discount", + "description": "Create a new discount with the provided details", + "requestBody": { + "description": "Discount details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.CreateDiscountRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.DiscountResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codegen-request-body-name": "discount" + } + }, + "/discounts/code/{code}": { + "get": { + "tags": [ + "Discount" + ], + "summary": "Get a discount by code", + "description": "Get a discount by its unique code", + "parameters": [ + { + "name": "code", + "in": "path", + "description": "Discount code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.DiscountResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/discounts/{id}": { + "get": { + "tags": [ + "Discount" + ], + "summary": "Get a discount by ID", + "description": "Get a discount by its unique identifier", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Discount ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.DiscountResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "put": { + "tags": [ + "Discount" + ], + "summary": "Update a discount by ID", + "description": "Update a discount by its unique identifier", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Discount ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Discount details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.UpdateDiscountRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.DiscountResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codegen-request-body-name": "discount" + }, + "delete": { + "tags": [ + "Discount" + ], + "summary": "Delete a discount by ID", + "description": "Delete a discount by its unique identifier", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Discount ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content", + "content": {} + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/internships": { + "get": { + "tags": [ + "Internship" + ], + "summary": "List internships", + "description": "List internships with optional filtering", + "parameters": [ + { + "name": "category_ids", + "in": "query", + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "duration_in_weeks", + "in": "query", + "description": "These fields are used to filter internships by duration in weeks", + "schema": { + "maximum": 52, + "minimum": 1, + "type": "integer" + } + }, + { + "name": "end_time", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "expand", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "internship_ids", + "in": "query", + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "levels", + "in": "query", + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "beginner", + "intermediate", + "advanced" + ] + } + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "name": "max_price", + "in": "query", + "description": "These fields are used to filter internships by price", + "schema": { + "type": "number" + } + }, + { + "name": "min_price", + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "modes", + "in": "query", + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "remote", + "hybrid", + "onsite" + ] + } + } + }, + { + "name": "name", + "in": "query", + "description": "These fields are used to filter internships by category, level and mode", + "schema": { + "type": "string" + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "minimum": 0, + "type": "integer" + } + }, + { + "name": "order", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "start_time", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "published", + "deleted", + "archived", + "inactive", + "pending" + ], + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ] + }, + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.ListInternshipResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Internship" + ], + "summary": "Create a new internship", + "description": "Create a new internship with the provided details", + "requestBody": { + "description": "Internship details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.CreateInternshipRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.InternshipResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "x-codegen-request-body-name": "internship" + } + }, + "/internships/{id}": { + "get": { + "tags": [ + "Internship" + ], + "summary": "Get an internship by ID", + "description": "Get an internship by its unique identifier", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Internship ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.InternshipResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + } + }, + "put": { + "tags": [ + "Internship" + ], + "summary": "Update an internship", + "description": "Update an internship by its unique identifier", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Internship ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Internship details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.UpdateInternshipRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.InternshipResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "x-codegen-request-body-name": "internship" + }, + "delete": { + "tags": [ + "Internship" + ], + "summary": "Delete an internship", + "description": "Delete an internship by its unique identifier", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Internship ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content", + "content": {} + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + } + } + }, + "/user": { + "put": { + "tags": [ + "User" + ], + "summary": "Update current user", + "description": "Update the current user's information", + "requestBody": { + "description": "Update user request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.UpdateUserRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.MeResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + }, + "x-codegen-request-body-name": "request" + } + }, + "/user/me": { + "get": { + "tags": [ + "User" + ], + "summary": "Get current user", + "description": "Get the current user's information", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dto.MeResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ierr.ErrorResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "dto.CategoryResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "description": { + "type": "string", + "description": "Description holds the value of the \"description\" field." + }, + "id": { + "type": "string", + "description": "ID of the ent." + }, + "internships": { + "type": "array", + "description": "internships holds the value of the internships edge.", + "items": { + "$ref": "#/components/schemas/internship.Internship" + } + }, + "lookup_key": { + "type": "string", + "description": "LookupKey holds the value of the \"lookup_key\" field." + }, + "name": { + "type": "string", + "description": "Name holds the value of the \"name\" field." + }, + "status": { + "$ref": "#/components/schemas/types.Status" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, + "dto.CreateCategoryRequest": { + "required": [ + "lookup_key", + "name" + ], + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "lookup_key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "dto.CreateDiscountRequest": { + "required": [ + "code", + "discount_type", + "discount_value" + ], + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "discount_type": { + "$ref": "#/components/schemas/types.DiscountType" + }, + "discount_value": { + "type": "number" + }, + "is_active": { + "type": "boolean" + }, + "is_combinable": { + "type": "boolean" + }, + "max_uses": { + "type": "integer" + }, + "metadata": { + "$ref": "#/components/schemas/types.Metadata" + }, + "min_order_value": { + "type": "number" + }, + "valid_from": { + "type": "string" + }, + "valid_until": { + "type": "string" + } + } + }, + "dto.CreateInternshipRequest": { + "required": [ + "currency", + "description", + "level", + "lookup_key", + "mode", + "price", + "title" + ], + "type": "object", + "properties": { + "benefits": { + "type": "array", + "items": { + "type": "string" + } + }, + "category_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "currency": { + "type": "string" + }, + "description": { + "minLength": 10, + "type": "string" + }, + "duration_in_weeks": { + "minimum": 0, + "type": "integer" + }, + "flat_discount": { + "type": "number" + }, + "learning_outcomes": { + "type": "array", + "items": { + "type": "string" + } + }, + "level": { + "$ref": "#/components/schemas/types.InternshipLevel" + }, + "lookup_key": { + "type": "string" + }, + "mode": { + "$ref": "#/components/schemas/types.InternshipMode" + }, + "percentage_discount": { + "type": "number" + }, + "prerequisites": { + "type": "array", + "items": { + "type": "string" + } + }, + "price": { + "type": "number" + }, + "skills": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "maxLength": 255, + "minLength": 3, + "type": "string" + } + } + }, + "dto.DiscountResponse": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "description": { + "type": "string" + }, + "discount_type": { + "$ref": "#/components/schemas/types.DiscountType" + }, + "discount_value": { + "type": "number" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_combinable": { + "type": "boolean" + }, + "max_uses": { + "type": "integer" + }, + "metadata": { + "$ref": "#/components/schemas/types.Metadata" + }, + "min_order_value": { + "type": "number" + }, + "status": { + "$ref": "#/components/schemas/types.Status" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + }, + "valid_from": { + "type": "string" + }, + "valid_until": { + "type": "string" + } + } + }, + "dto.InternshipResponse": { + "type": "object", + "properties": { + "benefits": { + "type": "array", + "description": "Benefits of the internship", + "items": { + "type": "string" + } + }, + "categories": { + "type": "array", + "description": "Categories holds the value of the categories edge.", + "items": { + "$ref": "#/components/schemas/internship.Category" + } + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "currency": { + "type": "string", + "description": "Currency of the internship" + }, + "description": { + "type": "string", + "description": "Description holds the value of the \"description\" field." + }, + "duration_in_weeks": { + "type": "integer", + "description": "Alternative to months for shorter internships" + }, + "flat_discount": { + "type": "number", + "description": "Flat discount on the internship" + }, + "id": { + "type": "string", + "description": "ID of the ent." + }, + "learning_outcomes": { + "type": "array", + "description": "What students will learn in the internship", + "items": { + "type": "string" + } + }, + "level": { + "$ref": "#/components/schemas/types.InternshipLevel" + }, + "lookup_key": { + "type": "string", + "description": "LookupKey holds the value of the \"lookup_key\" field." + }, + "mode": { + "$ref": "#/components/schemas/types.InternshipMode" + }, + "percentage_discount": { + "type": "number", + "description": "Percentage discount on the internship" + }, + "prerequisites": { + "type": "array", + "description": "Prerequisites or recommended knowledge", + "items": { + "type": "string" + } + }, + "price": { + "type": "number", + "description": "Price of the internship" + }, + "skills": { + "type": "array", + "description": "List of required skills", + "items": { + "type": "string" + } + }, + "status": { + "$ref": "#/components/schemas/types.Status" + }, + "title": { + "type": "string", + "description": "Title holds the value of the \"title\" field." + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, + "dto.ListCategoryResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dto.CategoryResponse" + } + }, + "pagination": { + "$ref": "#/components/schemas/types.PaginationResponse" + } + } + }, + "dto.ListDiscountResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dto.DiscountResponse" + } + }, + "pagination": { + "$ref": "#/components/schemas/types.PaginationResponse" + } + } + }, + "dto.ListInternshipResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dto.InternshipResponse" + } + }, + "pagination": { + "$ref": "#/components/schemas/types.PaginationResponse" + } + } + }, + "dto.MeResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "dto.SignupRequest": { + "required": [ + "access_token", + "email", + "full_name", + "role" + ], + "type": "object", + "properties": { + "access_token": { + "type": "string", + "description": "access token" + }, + "email": { + "type": "string", + "description": "basic info" + }, + "full_name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "role": { + "$ref": "#/components/schemas/types.UserRole" + } + } + }, + "dto.SignupResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "id": { + "type": "string" + } + } + }, + "dto.UpdateCategoryRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "lookup_key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "dto.UpdateDiscountRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_combinable": { + "type": "boolean" + }, + "max_uses": { + "type": "integer" + }, + "metadata": { + "$ref": "#/components/schemas/types.Metadata" + }, + "min_order_value": { + "type": "number" + }, + "valid_from": { + "type": "string" + }, + "valid_until": { + "type": "string" + } + } + }, + "dto.UpdateInternshipRequest": { + "type": "object", + "properties": { + "benefits": { + "type": "array", + "items": { + "type": "string" + } + }, + "category_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "currency": { + "type": "string" + }, + "description": { + "minLength": 10, + "type": "string" + }, + "duration_in_weeks": { + "minimum": 0, + "type": "integer" + }, + "flat_discount": { + "type": "number" + }, + "learning_outcomes": { + "type": "array", + "items": { + "type": "string" + } + }, + "level": { + "$ref": "#/components/schemas/types.InternshipLevel" + }, + "lookup_key": { + "type": "string" + }, + "mode": { + "$ref": "#/components/schemas/types.InternshipMode" + }, + "percentage_discount": { + "type": "number" + }, + "prerequisites": { + "type": "array", + "items": { + "type": "string" + } + }, + "price": { + "type": "number" + }, + "skills": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "maxLength": 255, + "minLength": 3, + "type": "string" + } + } + }, + "dto.UpdateUserRequest": { + "type": "object", + "properties": { + "full_name": { + "type": "string" + }, + "phone": { + "type": "string" + } + } + }, + "ierr.ErrorDetail": { + "type": "object", + "properties": { + "details": { + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "internal_error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "ierr.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ierr.ErrorDetail" + }, + "success": { + "type": "boolean" + } + } + }, + "internship.Category": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "description": { + "type": "string", + "description": "Description holds the value of the \"description\" field." + }, + "id": { + "type": "string", + "description": "ID of the ent." + }, + "internships": { + "type": "array", + "description": "internships holds the value of the internships edge.", + "items": { + "$ref": "#/components/schemas/internship.Internship" + } + }, + "lookup_key": { + "type": "string", + "description": "LookupKey holds the value of the \"lookup_key\" field." + }, + "name": { + "type": "string", + "description": "Name holds the value of the \"name\" field." + }, + "status": { + "$ref": "#/components/schemas/types.Status" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, + "internship.Internship": { + "type": "object", + "properties": { + "benefits": { + "type": "array", + "description": "Benefits of the internship", + "items": { + "type": "string" + } + }, + "categories": { + "type": "array", + "description": "Categories holds the value of the categories edge.", + "items": { + "$ref": "#/components/schemas/internship.Category" + } + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "currency": { + "type": "string", + "description": "Currency of the internship" + }, + "description": { + "type": "string", + "description": "Description holds the value of the \"description\" field." + }, + "duration_in_weeks": { + "type": "integer", + "description": "Alternative to months for shorter internships" + }, + "flat_discount": { + "type": "number", + "description": "Flat discount on the internship" + }, + "id": { + "type": "string", + "description": "ID of the ent." + }, + "learning_outcomes": { + "type": "array", + "description": "What students will learn in the internship", + "items": { + "type": "string" + } + }, + "level": { + "$ref": "#/components/schemas/types.InternshipLevel" + }, + "lookup_key": { + "type": "string", + "description": "LookupKey holds the value of the \"lookup_key\" field." + }, + "mode": { + "$ref": "#/components/schemas/types.InternshipMode" + }, + "percentage_discount": { + "type": "number", + "description": "Percentage discount on the internship" + }, + "prerequisites": { + "type": "array", + "description": "Prerequisites or recommended knowledge", + "items": { + "type": "string" + } + }, + "price": { + "type": "number", + "description": "Price of the internship" + }, + "skills": { + "type": "array", + "description": "List of required skills", + "items": { + "type": "string" + } + }, + "status": { + "$ref": "#/components/schemas/types.Status" + }, + "title": { + "type": "string", + "description": "Title holds the value of the \"title\" field." + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, + "types.DiscountType": { + "type": "string", + "enum": [ + "flat", + "percentage" + ], + "x-enum-varnames": [ + "DiscountTypeFlat", + "DiscountTypePercentage" + ] + }, + "types.InternshipLevel": { + "type": "string", + "enum": [ + "beginner", + "intermediate", + "advanced" + ], + "x-enum-varnames": [ + "InternshipLevelBeginner", + "InternshipLevelIntermediate", + "InternshipLevelAdvanced" + ] + }, + "types.InternshipMode": { + "type": "string", + "enum": [ + "remote", + "hybrid", + "onsite" + ], + "x-enum-varnames": [ + "InternshipModeRemote", + "InternshipModeHybrid", + "InternshipModeOnsite" + ] + }, + "types.Metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "types.PaginationResponse": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "types.Status": { + "type": "string", + "enum": [ + "published", + "deleted", + "archived", + "inactive", + "pending" + ], + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ] + }, + "types.UserRole": { + "type": "string", + "enum": [ + "STUDENT", + "INSTRUCTOR", + "ADMIN" + ], + "x-enum-varnames": [ + "UserRoleStudent", + "UserRoleInstructor", + "UserRoleAdmin" + ] + } + }, + "securitySchemes": { + "Authorization": { + "type": "apiKey", + "description": "Enter the token with the `Bearer ` prefix, e.g. `Bearer `.", + "name": "Authorization", + "in": "header" + } + } + }, + "x-original-swagger-version": "2.0" +} \ No newline at end of file diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go index d856d2f..f9fe6e3 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -102,9 +102,9 @@ var ( }, Indexes: []*schema.Index{ { - Name: "category_name", + Name: "category_lookup_key", Unique: true, - Columns: []*schema.Column{CategoriesColumns[7]}, + Columns: []*schema.Column{CategoriesColumns[8]}, }, }, } diff --git a/ent/schema/category.go b/ent/schema/category.go index 95441e2..2add61b 100644 --- a/ent/schema/category.go +++ b/ent/schema/category.go @@ -41,6 +41,7 @@ func (Category) Fields() []ent.Field { "postgres": "varchar(255)", }). NotEmpty(), + field.String("description"). SchemaType(map[string]string{ "postgres": "text", @@ -59,7 +60,7 @@ func (Category) Edges() []ent.Edge { // Indexes of the User. func (Category) Indexes() []ent.Index { return []ent.Index{ - index.Fields("name"). + index.Fields("lookup_key"). Unique(), } } diff --git a/go.sum b/go.sum index 248670b..1b3ed99 100644 --- a/go.sum +++ b/go.sum @@ -148,6 +148,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -164,6 +166,8 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= @@ -179,6 +183,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/razorpay/razorpay-go v1.3.4 h1:A9DZ18GZDn/bGRjQ9SesTGUNIAEw+IB27512l3I81aI= github.com/razorpay/razorpay-go v1.3.4/go.mod h1:VcljkUylUJAUEvFfGVv/d5ht1to1dUgF4H1+3nv7i+Q= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= @@ -198,6 +204,8 @@ github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= diff --git a/internal/config/config.go b/internal/config/config.go index 7ab1258..b22f3d7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -74,6 +74,8 @@ type SupabaseConfig struct { Key string `mapstructure:"key" validate:"required"` JWTSecret string `mapstructure:"jwt_secret" validate:"required"` ServiceKey string `mapstructure:"service_key" validate:"required"` + // Development API key for bypassing JWT validation in development + DevAPIKey string `mapstructure:"dev_api_key"` } type RazorpayConfig struct { diff --git a/internal/config/config.yaml b/internal/config/config.yaml index c9b9335..5398357 100644 --- a/internal/config/config.yaml +++ b/internal/config/config.yaml @@ -26,6 +26,7 @@ supabase: key: "dummy_key" jwt_secret: "dummy_jwt_secret" service_key: "dummy_service_key" + dev_api_key: "dev_api_key_for_development_only" # secrets secrets: diff --git a/internal/rest/middleware/auth.go b/internal/rest/middleware/auth.go index a29ea81..0500ac2 100644 --- a/internal/rest/middleware/auth.go +++ b/internal/rest/middleware/auth.go @@ -33,6 +33,7 @@ func GuestAuthenticateMiddleware(c *gin.Context) { // AuthenticateMiddleware is a middleware that authenticates requests based on either: // 1. JWT token in the Authorization header as a Bearer token +// 2. Development API key in the Authorization header (only in development mode) func AuthenticateMiddleware(cfg *config.Configuration, logger *logger.Logger) gin.HandlerFunc { return func(c *gin.Context) { @@ -47,7 +48,7 @@ func AuthenticateMiddleware(cfg *config.Configuration, logger *logger.Logger) gi authProvider := auth.NewSupabaseProvider(cfg, logger, encryptionService) - // If no API key, check for JWT token + // Get authorization header authHeader := c.GetHeader(types.HeaderAuthorization) if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) @@ -55,7 +56,18 @@ func AuthenticateMiddleware(cfg *config.Configuration, logger *logger.Logger) gi return } - // Check if the authorization header is in the correct format + // Check if it's a development API key (only in development mode) + if cfg.Server.Env == config.EnvLocal || cfg.Server.Env == config.EnvDev { + if cfg.Supabase.DevAPIKey != "" && authHeader == "Bearer "+cfg.Supabase.DevAPIKey { + // Use default development user + setContextValues(c, types.DefaultUserID, types.DefaultUserEmail, types.DefaultUserRole) + logger.Debugw("Using development API key for authentication", "user_id", types.DefaultUserID) + c.Next() + return + } + } + + // Check if the authorization header is in the correct format for JWT if !strings.HasPrefix(authHeader, "Bearer ") { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"}) c.Abort() diff --git a/scripts/dev-auth-example.sh b/scripts/dev-auth-example.sh new file mode 100755 index 0000000..fda4f16 --- /dev/null +++ b/scripts/dev-auth-example.sh @@ -0,0 +1,131 @@ +#!/bin/bash + +# Development Authentication Example Script +# This script demonstrates how to use the development authentication solutions + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}🚀 Development Authentication Examples${NC}" +echo "==================================" +echo + +# Check if server is running +echo -e "${YELLOW}1. Checking if server is running...${NC}" +if curl -s http://localhost:8080/health > /dev/null 2>&1; then + echo -e "${GREEN}✅ Server is running on http://localhost:8080${NC}" +else + echo -e "${RED}❌ Server is not running. Please start your server first.${NC}" + echo " Run: go run cmd/server/main.go" + exit 1 +fi + +echo + +# Example 1: Development API Key +echo -e "${YELLOW}2. Testing with Development API Key${NC}" +echo "----------------------------------------" + +# Check if dev API key is configured +if [ -z "$CAYGNUS_SUPABASE_DEV_API_KEY" ]; then + echo -e "${RED}❌ CAYGNUS_SUPABASE_DEV_API_KEY not set${NC}" + echo " Add to your .env file:" + echo " CAYGNUS_SUPABASE_DEV_API_KEY=your_dev_key_here" + echo +else + echo -e "${GREEN}✅ Development API key found${NC}" + echo "Testing authenticated endpoint..." + + response=$(curl -s -w "\n%{http_code}" \ + -H "Authorization: Bearer $CAYGNUS_SUPABASE_DEV_API_KEY" \ + http://localhost:8080/api/v1/user/me) + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | head -n -1) + + if [ "$http_code" = "200" ]; then + echo -e "${GREEN}✅ Success! (HTTP $http_code)${NC}" + echo "Response: $body" + else + echo -e "${RED}❌ Failed (HTTP $http_code)${NC}" + echo "Response: $body" + fi +fi + +echo + +# Example 2: Long-lived JWT Token +echo -e "${YELLOW}3. Testing with Long-lived JWT Token${NC}" +echo "----------------------------------------" + +if [ -z "$DEV_JWT_TOKEN" ]; then + echo -e "${RED}❌ DEV_JWT_TOKEN not set${NC}" + echo " To generate a token, run:" + echo " make generate-dev-token JWT_SECRET=your_jwt_secret" + echo " Then add the token to your .env file:" + echo " DEV_JWT_TOKEN=your_generated_token_here" + echo +else + echo -e "${GREEN}✅ Development JWT token found${NC}" + echo "Testing authenticated endpoint..." + + response=$(curl -s -w "\n%{http_code}" \ + -H "Authorization: Bearer $DEV_JWT_TOKEN" \ + http://localhost:8080/api/v1/user/me) + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | head -n -1) + + if [ "$http_code" = "200" ]; then + echo -e "${GREEN}✅ Success! (HTTP $http_code)${NC}" + echo "Response: $body" + else + echo -e "${RED}❌ Failed (HTTP $http_code)${NC}" + echo "Response: $body" + fi +fi + +echo + +# Example 3: Without authentication (should fail) +echo -e "${YELLOW}4. Testing without authentication (should fail)${NC}" +echo "------------------------------------------------" + +response=$(curl -s -w "\n%{http_code}" \ + http://localhost:8080/api/v1/user/me) + +http_code=$(echo "$response" | tail -n1) +body=$(echo "$response" | head -n -1) + +if [ "$http_code" = "401" ]; then + echo -e "${GREEN}✅ Correctly rejected (HTTP $http_code)${NC}" + echo "Response: $body" +else + echo -e "${RED}❌ Unexpected response (HTTP $http_code)${NC}" + echo "Response: $body" +fi + +echo + +# Summary +echo -e "${BLUE}📋 Summary${NC}" +echo "==========" +echo -e "${GREEN}✅ Development API Key:${NC} Fast, simple, no expiration" +echo -e "${GREEN}✅ Long-lived JWT Token:${NC} Real JWT validation, 1 year validity" +echo -e "${GREEN}✅ Both solutions:${NC} Only work in development mode" +echo +echo -e "${YELLOW}💡 Tips:${NC}" +echo "- Use Development API Key for quick testing" +echo "- Use Long-lived JWT Token for realistic testing" +echo "- Never use development tokens in production" +echo "- Keep your JWT secrets secure" +echo +echo -e "${BLUE}📚 Documentation:${NC}" +echo "- See docs/DEVELOPMENT_AUTHENTICATION.md for detailed guide" +echo "- Run 'make generate-dev-token JWT_SECRET=your_secret' to generate tokens" \ No newline at end of file From f57b6ca3c954d71e08e6e9ba7d2bc41d977db2be Mon Sep 17 00:00:00 2001 From: omkar sonawane Date: Sun, 6 Jul 2025 20:19:34 +0530 Subject: [PATCH 06/10] Remove development authentication example script and clean up main command list in main.go --- scripts/dev-auth-example.sh | 131 ------------------------------------ scripts/main.go | 3 - 2 files changed, 134 deletions(-) delete mode 100755 scripts/dev-auth-example.sh diff --git a/scripts/dev-auth-example.sh b/scripts/dev-auth-example.sh deleted file mode 100755 index fda4f16..0000000 --- a/scripts/dev-auth-example.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/bin/bash - -# Development Authentication Example Script -# This script demonstrates how to use the development authentication solutions - -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}🚀 Development Authentication Examples${NC}" -echo "==================================" -echo - -# Check if server is running -echo -e "${YELLOW}1. Checking if server is running...${NC}" -if curl -s http://localhost:8080/health > /dev/null 2>&1; then - echo -e "${GREEN}✅ Server is running on http://localhost:8080${NC}" -else - echo -e "${RED}❌ Server is not running. Please start your server first.${NC}" - echo " Run: go run cmd/server/main.go" - exit 1 -fi - -echo - -# Example 1: Development API Key -echo -e "${YELLOW}2. Testing with Development API Key${NC}" -echo "----------------------------------------" - -# Check if dev API key is configured -if [ -z "$CAYGNUS_SUPABASE_DEV_API_KEY" ]; then - echo -e "${RED}❌ CAYGNUS_SUPABASE_DEV_API_KEY not set${NC}" - echo " Add to your .env file:" - echo " CAYGNUS_SUPABASE_DEV_API_KEY=your_dev_key_here" - echo -else - echo -e "${GREEN}✅ Development API key found${NC}" - echo "Testing authenticated endpoint..." - - response=$(curl -s -w "\n%{http_code}" \ - -H "Authorization: Bearer $CAYGNUS_SUPABASE_DEV_API_KEY" \ - http://localhost:8080/api/v1/user/me) - - http_code=$(echo "$response" | tail -n1) - body=$(echo "$response" | head -n -1) - - if [ "$http_code" = "200" ]; then - echo -e "${GREEN}✅ Success! (HTTP $http_code)${NC}" - echo "Response: $body" - else - echo -e "${RED}❌ Failed (HTTP $http_code)${NC}" - echo "Response: $body" - fi -fi - -echo - -# Example 2: Long-lived JWT Token -echo -e "${YELLOW}3. Testing with Long-lived JWT Token${NC}" -echo "----------------------------------------" - -if [ -z "$DEV_JWT_TOKEN" ]; then - echo -e "${RED}❌ DEV_JWT_TOKEN not set${NC}" - echo " To generate a token, run:" - echo " make generate-dev-token JWT_SECRET=your_jwt_secret" - echo " Then add the token to your .env file:" - echo " DEV_JWT_TOKEN=your_generated_token_here" - echo -else - echo -e "${GREEN}✅ Development JWT token found${NC}" - echo "Testing authenticated endpoint..." - - response=$(curl -s -w "\n%{http_code}" \ - -H "Authorization: Bearer $DEV_JWT_TOKEN" \ - http://localhost:8080/api/v1/user/me) - - http_code=$(echo "$response" | tail -n1) - body=$(echo "$response" | head -n -1) - - if [ "$http_code" = "200" ]; then - echo -e "${GREEN}✅ Success! (HTTP $http_code)${NC}" - echo "Response: $body" - else - echo -e "${RED}❌ Failed (HTTP $http_code)${NC}" - echo "Response: $body" - fi -fi - -echo - -# Example 3: Without authentication (should fail) -echo -e "${YELLOW}4. Testing without authentication (should fail)${NC}" -echo "------------------------------------------------" - -response=$(curl -s -w "\n%{http_code}" \ - http://localhost:8080/api/v1/user/me) - -http_code=$(echo "$response" | tail -n1) -body=$(echo "$response" | head -n -1) - -if [ "$http_code" = "401" ]; then - echo -e "${GREEN}✅ Correctly rejected (HTTP $http_code)${NC}" - echo "Response: $body" -else - echo -e "${RED}❌ Unexpected response (HTTP $http_code)${NC}" - echo "Response: $body" -fi - -echo - -# Summary -echo -e "${BLUE}📋 Summary${NC}" -echo "==========" -echo -e "${GREEN}✅ Development API Key:${NC} Fast, simple, no expiration" -echo -e "${GREEN}✅ Long-lived JWT Token:${NC} Real JWT validation, 1 year validity" -echo -e "${GREEN}✅ Both solutions:${NC} Only work in development mode" -echo -echo -e "${YELLOW}💡 Tips:${NC}" -echo "- Use Development API Key for quick testing" -echo "- Use Long-lived JWT Token for realistic testing" -echo "- Never use development tokens in production" -echo "- Keep your JWT secrets secure" -echo -echo -e "${BLUE}📚 Documentation:${NC}" -echo "- See docs/DEVELOPMENT_AUTHENTICATION.md for detailed guide" -echo "- Run 'make generate-dev-token JWT_SECRET=your_secret' to generate tokens" \ No newline at end of file diff --git a/scripts/main.go b/scripts/main.go index 5026405..1ad451e 100644 --- a/scripts/main.go +++ b/scripts/main.go @@ -41,9 +41,6 @@ func main() { fmt.Println("\nExamples:") fmt.Println(" go run scripts/main.go -list # List all available commands") fmt.Println(" go run scripts/main.go -cmd generate-ent # Generate Ent schema") - fmt.Println(" go run scripts/main.go -cmd seed-ranks # Seed rank data") - fmt.Println(" go run scripts/main.go -cmd clear-ranks # Clear all ranks") - fmt.Println(" go run scripts/main.go -cmd validate-ranks # Validate seeded ranks") fmt.Println("\nNote: Ensure your database configuration is properly set up before running database commands.") } From 956d0472a107f428760243d50e55f03959b29bbf Mon Sep 17 00:00:00 2001 From: omkar sonawane Date: Sun, 6 Jul 2025 21:12:36 +0530 Subject: [PATCH 07/10] Refactor discount validation logic, update default user email, and add database seeding command with sample data generation --- internal/api/dto/discount.go | 6 - internal/types/context.go | 2 +- scripts/internal/seed_data.go | 512 ++++++++++++++++++++++++++++++++++ scripts/main.go | 6 + 4 files changed, 519 insertions(+), 7 deletions(-) create mode 100644 scripts/internal/seed_data.go diff --git a/internal/api/dto/discount.go b/internal/api/dto/discount.go index e225513..9f1f245 100644 --- a/internal/api/dto/discount.go +++ b/internal/api/dto/discount.go @@ -31,12 +31,6 @@ func (r *CreateDiscountRequest) Validate() error { return err } - if r.ValidFrom != nil && r.ValidFrom.Before(time.Now()) { - return ierr.NewError("valid_from must be in the future"). - WithHint("Valid from must be in the future"). - Mark(ierr.ErrValidation) - } - if r.ValidFrom != nil && r.ValidUntil != nil && r.ValidFrom.After(lo.FromPtr(r.ValidUntil)) { return ierr.NewError("valid_from must be before valid_until"). WithHint("Valid from must be before valid until"). diff --git a/internal/types/context.go b/internal/types/context.go index 33e4886..d7d5a0e 100644 --- a/internal/types/context.go +++ b/internal/types/context.go @@ -19,7 +19,7 @@ const ( // Default values DefaultUserID = "00000000-0000-0000-0000-000000000000" DefaultRequestID = "00000000-0000-0000-0000-000000000000" - DefaultUserEmail = "test@test.com" + DefaultUserEmail = "demo-user@test.com" DefaultUserRole = UserRoleAdmin DefaultIsGuest = true ) diff --git a/scripts/internal/seed_data.go b/scripts/internal/seed_data.go new file mode 100644 index 0000000..15142cb --- /dev/null +++ b/scripts/internal/seed_data.go @@ -0,0 +1,512 @@ +package internal + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/omkar273/codegeeky/internal/api/dto" + "github.com/omkar273/codegeeky/internal/config" + "github.com/omkar273/codegeeky/internal/logger" + "github.com/omkar273/codegeeky/internal/postgres" + "github.com/omkar273/codegeeky/internal/repository" + "github.com/omkar273/codegeeky/internal/service" + "github.com/omkar273/codegeeky/internal/types" + "github.com/shopspring/decimal" +) + +// mockWebhookPublisher is a simple mock implementation for seeding +type mockWebhookPublisher struct{} + +func (m *mockWebhookPublisher) PublishWebhook(ctx context.Context, event *types.WebhookEvent) error { + // Do nothing for seeding - just return success + return nil +} + +func (m *mockWebhookPublisher) Close() error { + // Do nothing for seeding + return nil +} + +// SeedData seeds the database with sample data using the service layer +func SeedData() error { + // Load configuration + cfg, err := config.NewConfig() + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + + // Initialize logger + logger, err := logger.NewLogger(cfg) + if err != nil { + return fmt.Errorf("failed to create logger: %w", err) + } + + // Initialize database client + entClient, err := postgres.NewEntClient(cfg, logger) + if err != nil { + return fmt.Errorf("failed to create database client: %w", err) + } + defer entClient.Close() + + // Create postgres client wrapper + dbClient := postgres.NewClient(entClient, logger) + + // Create repository params + repoParams := repository.RepositoryParams{ + Client: dbClient, + Logger: logger, + Config: cfg, + } + + // Create repositories + userRepo := repository.NewUserRepository(repoParams) + discountRepo := repository.NewDiscountRepository(repoParams) + paymentRepo := repository.NewPaymentRepository(repoParams) + internshipRepo := repository.NewInternshipRepository(repoParams) + internshipBatchRepo := repository.NewInternshipBatchRepository(repoParams) + categoryRepo := repository.NewCategoryRepository(repoParams) + internshipEnrollmentRepo := repository.NewInternshipEnrollmentRepository(repoParams) + cartRepo := repository.NewCartRepository(repoParams) + + // Create service params + serviceParams := service.ServiceParams{ + Logger: logger, + Config: cfg, + DB: dbClient, + UserRepo: userRepo, + DiscountRepo: discountRepo, + PaymentRepo: paymentRepo, + InternshipRepo: internshipRepo, + InternshipBatchRepo: internshipBatchRepo, + CategoryRepo: categoryRepo, + InternshipEnrollmentRepo: internshipEnrollmentRepo, + CartRepo: cartRepo, + WebhookPublisher: &mockWebhookPublisher{}, // Use mock publisher for seeding + HTTPClient: nil, // Not needed for seeding + } + + // Create services + categoryService := service.NewCategoryService(serviceParams) + internshipService := service.NewInternshipService(serviceParams) + discountService := service.NewDiscountService(serviceParams) + + ctx := context.Background() + + log.Println("🌱 Starting database seeding...") + + // Step 1: Create Categories + log.Println("📂 Creating categories...") + categories, err := createCategories(ctx, categoryService) + if err != nil { + return fmt.Errorf("failed to create categories: %w", err) + } + log.Printf("✅ Created %d categories", len(categories)) + + // Step 2: Create Internships with category relationships + log.Println("💼 Creating internships...") + internships, err := createInternships(ctx, internshipService, categories) + if err != nil { + return fmt.Errorf("failed to create internships: %w", err) + } + log.Printf("✅ Created %d internships", len(internships)) + + // Step 3: Create Discounts + log.Println("🎫 Creating discounts...") + discounts, err := createDiscounts(ctx, discountService) + if err != nil { + return fmt.Errorf("failed to create discounts: %w", err) + } + log.Printf("✅ Created %d discounts", len(discounts)) + + log.Println("🎉 Database seeding completed successfully!") + log.Println("📊 Summary:") + log.Printf(" - Categories: %d", len(categories)) + log.Printf(" - Internships: %d", len(internships)) + log.Printf(" - Discounts: %d", len(discounts)) + + return nil +} + +// createCategories creates sample categories using the category service +func createCategories(ctx context.Context, categoryService service.CategoryService) ([]*dto.CategoryResponse, error) { + categories := []struct { + name string + lookupKey string + description string + }{ + { + name: "Web Development", + lookupKey: "web-development", + description: "Full-stack web development internships covering frontend and backend technologies", + }, + { + name: "Mobile Development", + lookupKey: "mobile-development", + description: "Mobile app development for iOS and Android platforms", + }, + { + name: "Data Science", + lookupKey: "data-science", + description: "Data analysis, machine learning, and statistical modeling", + }, + { + name: "DevOps", + lookupKey: "devops", + description: "Infrastructure, deployment, and automation practices", + }, + { + name: "UI/UX Design", + lookupKey: "ui-ux-design", + description: "User interface and user experience design", + }, + { + name: "Cybersecurity", + lookupKey: "cybersecurity", + description: "Security testing, threat analysis, and secure coding practices", + }, + { + name: "Cloud Computing", + lookupKey: "cloud-computing", + description: "AWS, Azure, and Google Cloud platform development", + }, + { + name: "Blockchain", + lookupKey: "blockchain", + description: "Blockchain development and smart contract programming", + }, + } + + var createdCategories []*dto.CategoryResponse + + for _, cat := range categories { + req := &dto.CreateCategoryRequest{ + Name: cat.name, + LookupKey: cat.lookupKey, + Description: cat.description, + } + + category, err := categoryService.Create(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to create category %s: %w", cat.name, err) + } + + createdCategories = append(createdCategories, category) + } + + return createdCategories, nil +} + +// createInternships creates sample internships with category relationships using the internship service +func createInternships(ctx context.Context, internshipService service.InternshipService, categories []*dto.CategoryResponse) ([]*dto.InternshipResponse, error) { + internships := []struct { + title string + lookupKey string + description string + skills []string + level types.InternshipLevel + mode types.InternshipMode + durationInWeeks int + learningOutcomes []string + prerequisites []string + benefits []string + currency string + price decimal.Decimal + flatDiscount *decimal.Decimal + percentageDiscount *decimal.Decimal + categoryIndex int // Index in categories slice + }{ + { + title: "Full-Stack Web Development", + lookupKey: "fullstack-web-dev", + description: "Learn modern full-stack web development with React, Node.js, and PostgreSQL. Build real-world applications and deploy them to production.", + skills: []string{"JavaScript", "React", "Node.js", "PostgreSQL", "Git", "Docker"}, + level: types.InternshipLevelIntermediate, + mode: types.InternshipModeRemote, + durationInWeeks: 12, + learningOutcomes: []string{ + "Build responsive web applications", + "Implement RESTful APIs", + "Database design and optimization", + "Deployment and CI/CD practices", + }, + prerequisites: []string{"Basic JavaScript knowledge", "Understanding of HTML/CSS"}, + benefits: []string{ + "Portfolio of real projects", + "Industry mentorship", + "Job placement assistance", + "Certificate of completion", + }, + currency: "USD", + price: decimal.NewFromInt(2999), + categoryIndex: 0, // Web Development + }, + { + title: "React Native Mobile Development", + lookupKey: "react-native-mobile", + description: "Master cross-platform mobile development with React Native. Build apps for both iOS and Android from a single codebase.", + skills: []string{"JavaScript", "React Native", "Redux", "Firebase", "App Store", "Google Play"}, + level: types.InternshipLevelAdvanced, + mode: types.InternshipModeHybrid, + durationInWeeks: 16, + learningOutcomes: []string{ + "Cross-platform mobile development", + "State management with Redux", + "App store deployment", + "Performance optimization", + }, + prerequisites: []string{"React fundamentals", "JavaScript ES6+"}, + benefits: []string{ + "Published app on stores", + "Performance optimization skills", + "Real-world project experience", + }, + currency: "USD", + price: decimal.NewFromInt(3999), + flatDiscount: &[]decimal.Decimal{decimal.NewFromInt(500)}[0], + categoryIndex: 1, // Mobile Development + }, + { + title: "Machine Learning Fundamentals", + lookupKey: "ml-fundamentals", + description: "Introduction to machine learning with Python. Learn algorithms, data preprocessing, and model deployment.", + skills: []string{"Python", "Scikit-learn", "Pandas", "NumPy", "Matplotlib", "Jupyter"}, + level: types.InternshipLevelBeginner, + mode: types.InternshipModeRemote, + durationInWeeks: 10, + learningOutcomes: []string{ + "Supervised and unsupervised learning", + "Data preprocessing techniques", + "Model evaluation and validation", + "Feature engineering", + }, + prerequisites: []string{"Basic Python programming", "High school mathematics"}, + benefits: []string{ + "ML project portfolio", + "Kaggle competition experience", + "Industry case studies", + }, + currency: "USD", + price: decimal.NewFromInt(2499), + percentageDiscount: &[]decimal.Decimal{decimal.NewFromInt(15)}[0], + categoryIndex: 2, // Data Science + }, + { + title: "DevOps Engineering", + lookupKey: "devops-engineering", + description: "Learn modern DevOps practices including CI/CD, containerization, and cloud infrastructure management.", + skills: []string{"Docker", "Kubernetes", "AWS", "Jenkins", "Terraform", "Linux"}, + level: types.InternshipLevelIntermediate, + mode: types.InternshipModeOnsite, + durationInWeeks: 14, + learningOutcomes: []string{ + "Container orchestration", + "Infrastructure as Code", + "CI/CD pipeline design", + "Cloud architecture", + }, + prerequisites: []string{"Basic Linux commands", "Understanding of networking"}, + benefits: []string{ + "Hands-on infrastructure experience", + "AWS certification preparation", + "Real deployment scenarios", + }, + currency: "USD", + price: decimal.NewFromInt(3499), + categoryIndex: 3, // DevOps + }, + { + title: "UI/UX Design Masterclass", + lookupKey: "ui-ux-masterclass", + description: "Master the principles of user interface and user experience design. Create beautiful, functional, and user-friendly designs.", + skills: []string{"Figma", "Adobe XD", "Sketch", "Prototyping", "User Research", "Design Systems"}, + level: types.InternshipLevelAdvanced, + mode: types.InternshipModeHybrid, + durationInWeeks: 12, + learningOutcomes: []string{ + "User research and personas", + "Wireframing and prototyping", + "Design system creation", + "Usability testing", + }, + prerequisites: []string{"Basic design principles", "Familiarity with design tools"}, + benefits: []string{ + "Professional design portfolio", + "Industry design challenges", + "Design system certification", + }, + currency: "USD", + price: decimal.NewFromInt(2799), + categoryIndex: 4, // UI/UX Design + }, + { + title: "Cybersecurity Fundamentals", + lookupKey: "cybersecurity-fundamentals", + description: "Learn essential cybersecurity concepts including ethical hacking, secure coding, and threat analysis.", + skills: []string{"Python", "Wireshark", "Metasploit", "OWASP", "Network Security", "Cryptography"}, + level: types.InternshipLevelBeginner, + mode: types.InternshipModeRemote, + durationInWeeks: 8, + learningOutcomes: []string{ + "Network security analysis", + "Vulnerability assessment", + "Secure coding practices", + "Incident response", + }, + prerequisites: []string{"Basic networking knowledge", "Programming fundamentals"}, + benefits: []string{ + "Security certification prep", + "Real-world security challenges", + "Industry security tools", + }, + currency: "USD", + price: decimal.NewFromInt(1999), + categoryIndex: 5, // Cybersecurity + }, + } + + var createdInternships []*dto.InternshipResponse + + for _, intern := range internships { + // Get category ID + if intern.categoryIndex >= len(categories) { + return nil, fmt.Errorf("invalid category index %d for internship %s", intern.categoryIndex, intern.title) + } + categoryID := categories[intern.categoryIndex].ID + + req := &dto.CreateInternshipRequest{ + Title: intern.title, + LookupKey: intern.lookupKey, + Description: intern.description, + Skills: intern.skills, + Level: intern.level, + Mode: intern.mode, + DurationInWeeks: intern.durationInWeeks, + LearningOutcomes: intern.learningOutcomes, + Prerequisites: intern.prerequisites, + Benefits: intern.benefits, + Currency: intern.currency, + Price: intern.price, + FlatDiscount: intern.flatDiscount, + PercentageDiscount: intern.percentageDiscount, + CategoryIDs: []string{categoryID}, + } + + internship, err := internshipService.Create(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to create internship %s: %w", intern.title, err) + } + + createdInternships = append(createdInternships, internship) + } + + return createdInternships, nil +} + +// createDiscounts creates sample discounts using the discount service +func createDiscounts(ctx context.Context, discountService service.DiscountService) ([]*dto.DiscountResponse, error) { + discounts := []struct { + code string + description string + discountType types.DiscountType + discountValue decimal.Decimal + validFrom time.Time + validUntil *time.Time + isActive bool + maxUses *int + minOrderValue *decimal.Decimal + isCombinable bool + }{ + { + code: "WELCOME20", + description: "Welcome discount for new students", + discountType: types.DiscountTypePercentage, + discountValue: decimal.NewFromInt(20), + validFrom: time.Now(), + validUntil: &[]time.Time{time.Now().AddDate(0, 3, 0)}[0], // 3 months + isActive: true, + maxUses: &[]int{100}[0], + minOrderValue: &[]decimal.Decimal{decimal.NewFromInt(1000)}[0], + isCombinable: false, + }, + { + code: "FLAT500", + description: "Flat discount for premium courses", + discountType: types.DiscountTypeFlat, + discountValue: decimal.NewFromInt(500), + validFrom: time.Now(), + validUntil: &[]time.Time{time.Now().AddDate(0, 6, 0)}[0], // 6 months + isActive: true, + maxUses: &[]int{50}[0], + minOrderValue: &[]decimal.Decimal{decimal.NewFromInt(2000)}[0], + isCombinable: true, + }, + { + code: "SUMMER15", + description: "Summer special discount", + discountType: types.DiscountTypePercentage, + discountValue: decimal.NewFromInt(15), + validFrom: time.Now(), + validUntil: &[]time.Time{time.Now().AddDate(0, 2, 0)}[0], // 2 months + isActive: true, + maxUses: &[]int{200}[0], + minOrderValue: &[]decimal.Decimal{decimal.NewFromInt(500)}[0], + isCombinable: false, + }, + { + code: "BULK25", + description: "Bulk purchase discount", + discountType: types.DiscountTypePercentage, + discountValue: decimal.NewFromInt(25), + validFrom: time.Now(), + validUntil: &[]time.Time{time.Now().AddDate(1, 0, 0)}[0], // 1 year + isActive: true, + maxUses: &[]int{25}[0], + minOrderValue: &[]decimal.Decimal{decimal.NewFromInt(5000)}[0], + isCombinable: false, + }, + { + code: "EARLYBIRD100", + description: "Early bird flat discount", + discountType: types.DiscountTypeFlat, + discountValue: decimal.NewFromInt(100), + validFrom: time.Now(), + validUntil: &[]time.Time{time.Now().AddDate(0, 1, 0)}[0], // 1 month + isActive: true, + maxUses: &[]int{75}[0], + minOrderValue: &[]decimal.Decimal{decimal.NewFromInt(1500)}[0], + isCombinable: true, + }, + } + + var createdDiscounts []*dto.DiscountResponse + + for _, disc := range discounts { + req := &dto.CreateDiscountRequest{ + Code: disc.code, + Description: disc.description, + DiscountType: disc.discountType, + DiscountValue: disc.discountValue, + ValidFrom: &disc.validFrom, + ValidUntil: disc.validUntil, + IsActive: &disc.isActive, + MaxUses: disc.maxUses, + MinOrderValue: disc.minOrderValue, + IsCombinable: disc.isCombinable, + Metadata: types.Metadata{ + "created_by": "seed_script", + "purpose": "sample_data", + }, + } + + discount, err := discountService.Create(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to create discount %s: %w", disc.code, err) + } + + createdDiscounts = append(createdDiscounts, discount) + } + + return createdDiscounts, nil +} diff --git a/scripts/main.go b/scripts/main.go index 1ad451e..d020864 100644 --- a/scripts/main.go +++ b/scripts/main.go @@ -21,6 +21,11 @@ var commands = []Command{ Description: "Generate the Ent schema", Run: internal.GenerateEnt, }, + { + Name: "seed-data", + Description: "Seed the database with sample data (categories, internships, discounts)", + Run: internal.SeedData, + }, } func main() { @@ -41,6 +46,7 @@ func main() { fmt.Println("\nExamples:") fmt.Println(" go run scripts/main.go -list # List all available commands") fmt.Println(" go run scripts/main.go -cmd generate-ent # Generate Ent schema") + fmt.Println(" go run scripts/main.go -cmd seed-data # Seed database with sample data") fmt.Println("\nNote: Ensure your database configuration is properly set up before running database commands.") } From 9a6f8c12d434a6f90d3bb9babf49d4dd6a01fd57 Mon Sep 17 00:00:00 2001 From: omkar sonawane Date: Sun, 6 Jul 2025 22:19:42 +0530 Subject: [PATCH 08/10] Add cart and internship batch functionality to API, including CRUD operations and Swagger documentation updates --- cmd/server/main.go | 17 +- docs/swagger/docs.go | 1772 ++++++++++++++--- docs/swagger/swagger-3-0.json | 2295 +--------------------- docs/swagger/swagger.json | 1814 ++++++++++++++--- docs/swagger/swagger.yaml | 1061 +++++++++- internal/api/router.go | 45 +- internal/api/v1/cart.go | 285 ++- internal/api/v1/category.go | 2 +- internal/api/v1/internship.go | 2 +- internal/api/v1/internship_batch.go | 188 ++ internal/service/cart.go | 29 + internal/testutil/inmemory_cart_store.go | 2 +- internal/types/internship.go | 2 - 13 files changed, 4630 insertions(+), 2884 deletions(-) create mode 100644 internal/api/v1/internship_batch.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 6d7dc85..0434a05 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -121,6 +121,7 @@ func main() { service.NewPricingService, service.NewPaymentService, service.NewInternshipEnrollmentService, + service.NewCartService, )) // factory layer @@ -163,16 +164,20 @@ func provideHandlers( authService service.AuthService, userService service.UserService, internshipService service.InternshipService, + internshipBatchService service.InternshipBatchService, categoryService service.CategoryService, discountService service.DiscountService, + cartService service.CartService, ) *api.Handlers { return &api.Handlers{ - Health: v1.NewHealthHandler(logger), - Auth: v1.NewAuthHandler(authService), - User: v1.NewUserHandler(userService), - Internship: v1.NewInternshipHandler(internshipService, logger), - Category: v1.NewCategoryHandler(categoryService, logger), - Discount: v1.NewDiscountHandler(discountService, logger), + Health: v1.NewHealthHandler(logger), + Auth: v1.NewAuthHandler(authService), + User: v1.NewUserHandler(userService), + Internship: v1.NewInternshipHandler(internshipService, logger), + InternshipBatch: v1.NewInternshipBatchHandler(internshipBatchService, logger), + Category: v1.NewCategoryHandler(categoryService, logger), + Discount: v1.NewDiscountHandler(discountService, logger), + Cart: v1.NewCartHandler(cartService, logger), } } diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 791f940..6ce7357 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -53,6 +53,646 @@ const docTemplate = `{ } } }, + "/carts": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List carts with optional filtering", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "List carts", + "parameters": [ + { + "enum": [ + "onetime", + "default" + ], + "type": "string", + "x-enum-varnames": [ + "CartTypeOneTime", + "CartTypeDefault" + ], + "name": "cart_type", + "in": "query" + }, + { + "type": "string", + "name": "end_time", + "in": "query" + }, + { + "type": "string", + "name": "entity_id", + "in": "query" + }, + { + "enum": [ + "internship_batch", + "course" + ], + "type": "string", + "x-enum-varnames": [ + "CartLineItemEntityTypeInternshipBatch", + "CartLineItemEntityTypeCourse" + ], + "name": "entity_type", + "in": "query" + }, + { + "type": "string", + "name": "expand", + "in": "query" + }, + { + "type": "string", + "name": "expires_at", + "in": "query" + }, + { + "maximum": 1000, + "minimum": 1, + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "name": "order", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "start_time", + "in": "query" + }, + { + "enum": [ + "published", + "deleted", + "archived", + "inactive", + "pending" + ], + "type": "string", + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ], + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "These fields are used to filter carts by user id", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ListCartResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new cart with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Create a new cart", + "parameters": [ + { + "description": "Cart details", + "name": "cart", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateCartRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/dto.CartResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/carts/default": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the default cart for the authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Get user's default cart", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CartResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/carts/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get a cart by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Get a cart", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CartResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update a cart by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Update a cart", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Cart details", + "name": "cart", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateCartRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CartResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a cart by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Delete a cart", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/carts/{id}/line-items": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all line items for a specific cart", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Get cart line items", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/cart.CartLineItem" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add a new line item to a cart", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Add line item to cart", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Line item details", + "name": "line_item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateCartLineItemRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/cart.CartLineItem" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/carts/{id}/line-items/{line_item_id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get a specific line item by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Get line item", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Line Item ID", + "name": "line_item_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/cart.CartLineItem" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Remove a line item from a cart", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Remove line item from cart", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Line Item ID", + "name": "line_item_id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, "/categories": { "get": { "description": "List categories with optional filtering", @@ -65,7 +705,330 @@ const docTemplate = `{ "tags": [ "Category" ], - "summary": "List categories", + "summary": "List categories", + "parameters": [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "category_ids", + "in": "query" + }, + { + "type": "string", + "name": "end_time", + "in": "query" + }, + { + "type": "string", + "name": "expand", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "internship_ids", + "in": "query" + }, + { + "maximum": 1000, + "minimum": 1, + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "These fields are used to filter categories by name", + "name": "name", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "name": "order", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "start_time", + "in": "query" + }, + { + "enum": [ + "published", + "deleted", + "archived", + "inactive", + "pending" + ], + "type": "string", + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ], + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ListCategoryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "post": { + "description": "Create a new category with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Category" + ], + "summary": "Create a new category", + "parameters": [ + { + "description": "Category details", + "name": "category", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateCategoryRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/dto.CategoryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/categories/{id}": { + "get": { + "description": "Get a category by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Category" + ], + "summary": "Get a category by ID", + "parameters": [ + { + "type": "string", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CategoryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "put": { + "description": "Update a category by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Category" + ], + "summary": "Update a category", + "parameters": [ + { + "type": "string", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Category details", + "name": "category", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateCategoryRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CategoryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Delete a category by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Category" + ], + "summary": "Delete a category", + "parameters": [ + { + "type": "string", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/discounts": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List discounts with optional filtering", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discount" + ], + "summary": "List discounts", "parameters": [ { "type": "array", @@ -73,7 +1036,29 @@ const docTemplate = `{ "type": "string" }, "collectionFormat": "csv", - "name": "category_ids", + "name": "codes", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "discount_ids", + "in": "query" + }, + { + "enum": [ + "flat", + "percentage" + ], + "type": "string", + "x-enum-varnames": [ + "DiscountTypeFlat", + "DiscountTypePercentage" + ], + "name": "discount_type", "in": "query" }, { @@ -87,12 +1072,8 @@ const docTemplate = `{ "in": "query" }, { - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "csv", - "name": "internship_ids", + "type": "boolean", + "name": "is_combinable", "in": "query" }, { @@ -103,9 +1084,8 @@ const docTemplate = `{ "in": "query" }, { - "type": "string", - "description": "These fields are used to filter categories by name", - "name": "name", + "type": "number", + "name": "min_order_value", "in": "query" }, { @@ -151,13 +1131,23 @@ const docTemplate = `{ ], "name": "status", "in": "query" + }, + { + "type": "string", + "name": "valid_from", + "in": "query" + }, + { + "type": "string", + "name": "valid_until", + "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.ListCategoryResponse" + "$ref": "#/definitions/dto.ListDiscountResponse" } }, "400": { @@ -175,7 +1165,12 @@ const docTemplate = `{ } }, "post": { - "description": "Create a new category with the provided details", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new discount with the provided details", "consumes": [ "application/json" ], @@ -183,17 +1178,17 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Category" + "Discount" ], - "summary": "Create a new category", + "summary": "Create a new discount", "parameters": [ { - "description": "Category details", - "name": "category", + "description": "Discount details", + "name": "discount", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.CreateCategoryRequest" + "$ref": "#/definitions/dto.CreateDiscountRequest" } } ], @@ -201,7 +1196,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/dto.CategoryResponse" + "$ref": "#/definitions/dto.DiscountResponse" } }, "400": { @@ -219,9 +1214,14 @@ const docTemplate = `{ } } }, - "/categories/{id}": { + "/discounts/code/{code}": { "get": { - "description": "Get a category by its unique identifier", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get a discount by its unique code", "consumes": [ "application/json" ], @@ -229,13 +1229,62 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Category" + "Discount" ], - "summary": "Get a category by ID", + "summary": "Get a discount by code", + "parameters": [ + { + "type": "string", + "description": "Discount code", + "name": "code", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DiscountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/discounts/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get a discount by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discount" + ], + "summary": "Get a discount by ID", "parameters": [ { "type": "string", - "description": "Category ID", + "description": "Discount ID", "name": "id", "in": "path", "required": true @@ -245,7 +1294,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.CategoryResponse" + "$ref": "#/definitions/dto.DiscountResponse" } }, "400": { @@ -254,12 +1303,6 @@ const docTemplate = `{ "$ref": "#/definitions/ierr.ErrorResponse" } }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/ierr.ErrorResponse" - } - }, "500": { "description": "Internal Server Error", "schema": { @@ -269,7 +1312,12 @@ const docTemplate = `{ } }, "put": { - "description": "Update a category by its unique identifier", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update a discount by its unique identifier", "consumes": [ "application/json" ], @@ -277,24 +1325,24 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Category" + "Discount" ], - "summary": "Update a category", + "summary": "Update a discount by ID", "parameters": [ { "type": "string", - "description": "Category ID", + "description": "Discount ID", "name": "id", "in": "path", "required": true }, { - "description": "Category details", - "name": "category", + "description": "Discount details", + "name": "discount", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.UpdateCategoryRequest" + "$ref": "#/definitions/dto.UpdateDiscountRequest" } } ], @@ -302,7 +1350,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.CategoryResponse" + "$ref": "#/definitions/dto.DiscountResponse" } }, "400": { @@ -311,12 +1359,6 @@ const docTemplate = `{ "$ref": "#/definitions/ierr.ErrorResponse" } }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/ierr.ErrorResponse" - } - }, "500": { "description": "Internal Server Error", "schema": { @@ -326,7 +1368,12 @@ const docTemplate = `{ } }, "delete": { - "description": "Delete a category by its unique identifier", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a discount by its unique identifier", "consumes": [ "application/json" ], @@ -334,13 +1381,13 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Category" + "Discount" ], - "summary": "Delete a category", + "summary": "Delete a discount by ID", "parameters": [ { "type": "string", - "description": "Category ID", + "description": "Discount ID", "name": "id", "in": "path", "required": true @@ -356,12 +1403,6 @@ const docTemplate = `{ "$ref": "#/definitions/ierr.ErrorResponse" } }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/ierr.ErrorResponse" - } - }, "500": { "description": "Internal Server Error", "schema": { @@ -371,14 +1412,9 @@ const docTemplate = `{ } } }, - "/discounts": { + "/internshipbatches": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "List discounts with optional filtering", + "description": "List internship batches with optional filtering", "consumes": [ "application/json" ], @@ -386,21 +1422,31 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "List discounts", + "summary": "List internship batches", "parameters": [ { "enum": [ - "flat", - "percentage" + "upcoming", + "ongoing", + "completed", + "cancelled" ], "type": "string", "x-enum-varnames": [ - "DiscountTypeFlat", - "DiscountTypePercentage" + "InternshipBatchStatusUpcoming", + "InternshipBatchStatusOngoing", + "InternshipBatchStatusCompleted", + "InternshipBatchStatusCancelled" ], - "name": "discount_type", + "description": "These fields are used to filter internships by start and end date", + "name": "batch_status", + "in": "query" + }, + { + "type": "string", + "name": "end_date", "in": "query" }, { @@ -414,8 +1460,13 @@ const docTemplate = `{ "in": "query" }, { - "type": "boolean", - "name": "is_combinable", + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "description": "These fields are used to filter internships by internship id", + "name": "internship_ids", "in": "query" }, { @@ -426,8 +1477,9 @@ const docTemplate = `{ "in": "query" }, { - "type": "number", - "name": "min_order_value", + "type": "string", + "description": "These fields are used to filter internships by name", + "name": "name", "in": "query" }, { @@ -450,6 +1502,11 @@ const docTemplate = `{ "name": "sort", "in": "query" }, + { + "type": "string", + "name": "start_date", + "in": "query" + }, { "type": "string", "name": "start_time", @@ -473,23 +1530,13 @@ const docTemplate = `{ ], "name": "status", "in": "query" - }, - { - "type": "string", - "name": "valid_from", - "in": "query" - }, - { - "type": "string", - "name": "valid_until", - "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.ListDiscountResponse" + "$ref": "#/definitions/dto.ListInternshipBatchResponse" } }, "400": { @@ -507,12 +1554,7 @@ const docTemplate = `{ } }, "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create a new discount with the provided details", + "description": "Create a new internship batch with the provided details", "consumes": [ "application/json" ], @@ -520,17 +1562,17 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "Create a new discount", + "summary": "Create a new internship batch", "parameters": [ { - "description": "Discount details", - "name": "discount", + "description": "Internship batch details", + "name": "batch", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.CreateDiscountRequest" + "$ref": "#/definitions/dto.CreateInternshipBatchRequest" } } ], @@ -538,7 +1580,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/dto.DiscountResponse" + "$ref": "#/definitions/dto.InternshipBatchResponse" } }, "400": { @@ -556,14 +1598,9 @@ const docTemplate = `{ } } }, - "/discounts/code/{code}": { + "/internshipbatches/{id}": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get a discount by its unique code", + "description": "Get an internship batch by its unique identifier", "consumes": [ "application/json" ], @@ -571,14 +1608,14 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "Get a discount by code", + "summary": "Get an internship batch by ID", "parameters": [ { "type": "string", - "description": "Discount code", - "name": "code", + "description": "Internship Batch ID", + "name": "id", "in": "path", "required": true } @@ -587,7 +1624,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.DiscountResponse" + "$ref": "#/definitions/dto.InternshipBatchResponse" } }, "400": { @@ -596,51 +1633,8 @@ const docTemplate = `{ "$ref": "#/definitions/ierr.ErrorResponse" } }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/ierr.ErrorResponse" - } - } - } - } - }, - "/discounts/{id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get a discount by its unique identifier", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Discount" - ], - "summary": "Get a discount by ID", - "parameters": [ - { - "type": "string", - "description": "Discount ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/dto.DiscountResponse" - } - }, - "400": { - "description": "Bad Request", + "404": { + "description": "Not Found", "schema": { "$ref": "#/definitions/ierr.ErrorResponse" } @@ -654,12 +1648,7 @@ const docTemplate = `{ } }, "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update a discount by its unique identifier", + "description": "Update an internship batch by its unique identifier", "consumes": [ "application/json" ], @@ -667,24 +1656,24 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "Update a discount by ID", + "summary": "Update an internship batch", "parameters": [ { "type": "string", - "description": "Discount ID", + "description": "Internship Batch ID", "name": "id", "in": "path", "required": true }, { - "description": "Discount details", - "name": "discount", + "description": "Internship batch details", + "name": "batch", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.UpdateDiscountRequest" + "$ref": "#/definitions/dto.UpdateInternshipBatchRequest" } } ], @@ -692,7 +1681,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.DiscountResponse" + "$ref": "#/definitions/dto.InternshipBatchResponse" } }, "400": { @@ -701,6 +1690,12 @@ const docTemplate = `{ "$ref": "#/definitions/ierr.ErrorResponse" } }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, "500": { "description": "Internal Server Error", "schema": { @@ -710,12 +1705,7 @@ const docTemplate = `{ } }, "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete a discount by its unique identifier", + "description": "Delete an internship batch by its unique identifier", "consumes": [ "application/json" ], @@ -723,13 +1713,13 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "Delete a discount by ID", + "summary": "Delete an internship batch", "parameters": [ { "type": "string", - "description": "Discount ID", + "description": "Internship Batch ID", "name": "id", "in": "path", "required": true @@ -745,6 +1735,12 @@ const docTemplate = `{ "$ref": "#/definitions/ierr.ErrorResponse" } }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, "500": { "description": "Internal Server Error", "schema": { @@ -1199,9 +2195,121 @@ const docTemplate = `{ } } } - } - }, - "definitions": { + } + }, + "definitions": { + "cart.CartLineItem": { + "type": "object", + "properties": { + "cart_id": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "discount_amount": { + "type": "number" + }, + "entity_id": { + "type": "string" + }, + "entity_type": { + "$ref": "#/definitions/types.CartLineItemEntityType" + }, + "id": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "per_unit_price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "status": { + "$ref": "#/definitions/types.Status" + }, + "subtotal": { + "type": "number" + }, + "tax_amount": { + "type": "number" + }, + "total": { + "type": "number" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, + "dto.CartResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "discount_amount": { + "type": "number" + }, + "expires_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "line_items": { + "type": "array", + "items": { + "$ref": "#/definitions/cart.CartLineItem" + } + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "status": { + "$ref": "#/definitions/types.Status" + }, + "subtotal": { + "type": "number" + }, + "tax_amount": { + "type": "number" + }, + "total": { + "type": "number" + }, + "type": { + "$ref": "#/definitions/types.CartType" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + }, + "user_id": { + "type": "string" + } + } + }, "dto.CategoryResponse": { "type": "object", "properties": { @@ -1245,6 +2353,61 @@ const docTemplate = `{ } } }, + "dto.CreateCartLineItemRequest": { + "type": "object", + "required": [ + "entity_id", + "entity_type", + "quantity" + ], + "properties": { + "cart_id": { + "type": "string" + }, + "entity_id": { + "type": "string" + }, + "entity_type": { + "$ref": "#/definitions/types.CartLineItemEntityType" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "quantity": { + "type": "integer", + "minimum": 1 + } + } + }, + "dto.CreateCartRequest": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "expires_at": { + "type": "string" + }, + "line_items": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.CreateCartLineItemRequest" + } + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "type": { + "$ref": "#/definitions/types.CartType" + } + } + }, "dto.CreateCategoryRequest": { "type": "object", "required": [ @@ -1306,6 +2469,35 @@ const docTemplate = `{ } } }, + "dto.CreateInternshipBatchRequest": { + "type": "object", + "required": [ + "internship_id", + "name" + ], + "properties": { + "batch_status": { + "$ref": "#/definitions/types.InternshipBatchStatus" + }, + "description": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "internship_id": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 3 + }, + "start_date": { + "type": "string" + } + } + }, "dto.CreateInternshipRequest": { "type": "object", "required": [ @@ -1440,18 +2632,60 @@ const docTemplate = `{ } } }, + "dto.InternshipBatchResponse": { + "type": "object", + "properties": { + "batch_status": { + "$ref": "#/definitions/types.InternshipBatchStatus" + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "description": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "id": { + "type": "string" + }, + "internship_id": { + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/types.Metadata" + }, + "name": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/types.Status" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, "dto.InternshipResponse": { "type": "object", "properties": { "benefits": { - "description": "Benefits of the internship", "type": "array", "items": { "type": "string" } }, "categories": { - "description": "Categories holds the value of the categories edge.", "type": "array", "items": { "$ref": "#/definitions/internship.Category" @@ -1464,69 +2698,48 @@ const docTemplate = `{ "type": "string" }, "currency": { - "description": "Currency of the internship", "type": "string" }, "description": { - "description": "Description holds the value of the \"description\" field.", "type": "string" }, "duration_in_weeks": { - "description": "Alternative to months for shorter internships", "type": "integer" }, "flat_discount": { - "description": "Flat discount on the internship", "type": "number" }, "id": { - "description": "ID of the ent.", "type": "string" }, "learning_outcomes": { - "description": "What students will learn in the internship", "type": "array", "items": { "type": "string" } }, "level": { - "description": "Level of the internship: beginner, intermediate, advanced", - "allOf": [ - { - "$ref": "#/definitions/types.InternshipLevel" - } - ] + "$ref": "#/definitions/types.InternshipLevel" }, "lookup_key": { - "description": "LookupKey holds the value of the \"lookup_key\" field.", "type": "string" }, "mode": { - "description": "Internship mode: remote, hybrid, onsite", - "allOf": [ - { - "$ref": "#/definitions/types.InternshipMode" - } - ] + "$ref": "#/definitions/types.InternshipMode" }, "percentage_discount": { - "description": "Percentage discount on the internship", "type": "number" }, "prerequisites": { - "description": "Prerequisites or recommended knowledge", "type": "array", "items": { "type": "string" } }, "price": { - "description": "Price of the internship", "type": "number" }, "skills": { - "description": "List of required skills", "type": "array", "items": { "type": "string" @@ -1535,10 +2748,15 @@ const docTemplate = `{ "status": { "$ref": "#/definitions/types.Status" }, + "subtotal": { + "type": "number" + }, "title": { - "description": "Title holds the value of the \"title\" field.", "type": "string" }, + "total": { + "type": "number" + }, "updated_at": { "type": "string" }, @@ -1547,6 +2765,20 @@ const docTemplate = `{ } } }, + "dto.ListCartResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.CartResponse" + } + }, + "pagination": { + "$ref": "#/definitions/types.PaginationResponse" + } + } + }, "dto.ListCategoryResponse": { "type": "object", "properties": { @@ -1575,6 +2807,20 @@ const docTemplate = `{ } } }, + "dto.ListInternshipBatchResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.InternshipBatchResponse" + } + }, + "pagination": { + "$ref": "#/definitions/types.PaginationResponse" + } + } + }, "dto.ListInternshipResponse": { "type": "object", "properties": { @@ -1653,6 +2899,29 @@ const docTemplate = `{ } } }, + "dto.UpdateCartRequest": { + "type": "object", + "properties": { + "expires_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "line_items": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.CreateCartLineItemRequest" + } + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "dto.UpdateCategoryRequest": { "type": "object", "properties": { @@ -1696,6 +2965,28 @@ const docTemplate = `{ } } }, + "dto.UpdateInternshipBatchRequest": { + "type": "object", + "properties": { + "batch_status": { + "$ref": "#/definitions/types.InternshipBatchStatus" + }, + "description": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 3 + }, + "start_date": { + "type": "string" + } + } + }, "dto.UpdateInternshipRequest": { "type": "object", "properties": { @@ -1849,14 +3140,12 @@ const docTemplate = `{ "type": "object", "properties": { "benefits": { - "description": "Benefits of the internship", "type": "array", "items": { "type": "string" } }, "categories": { - "description": "Categories holds the value of the categories edge.", "type": "array", "items": { "$ref": "#/definitions/internship.Category" @@ -1869,69 +3158,48 @@ const docTemplate = `{ "type": "string" }, "currency": { - "description": "Currency of the internship", "type": "string" }, "description": { - "description": "Description holds the value of the \"description\" field.", "type": "string" }, "duration_in_weeks": { - "description": "Alternative to months for shorter internships", "type": "integer" }, "flat_discount": { - "description": "Flat discount on the internship", "type": "number" }, "id": { - "description": "ID of the ent.", "type": "string" }, "learning_outcomes": { - "description": "What students will learn in the internship", "type": "array", "items": { "type": "string" } }, "level": { - "description": "Level of the internship: beginner, intermediate, advanced", - "allOf": [ - { - "$ref": "#/definitions/types.InternshipLevel" - } - ] + "$ref": "#/definitions/types.InternshipLevel" }, "lookup_key": { - "description": "LookupKey holds the value of the \"lookup_key\" field.", "type": "string" }, "mode": { - "description": "Internship mode: remote, hybrid, onsite", - "allOf": [ - { - "$ref": "#/definitions/types.InternshipMode" - } - ] + "$ref": "#/definitions/types.InternshipMode" }, "percentage_discount": { - "description": "Percentage discount on the internship", "type": "number" }, "prerequisites": { - "description": "Prerequisites or recommended knowledge", "type": "array", "items": { "type": "string" } }, "price": { - "description": "Price of the internship", "type": "number" }, "skills": { - "description": "List of required skills", "type": "array", "items": { "type": "string" @@ -1940,10 +3208,15 @@ const docTemplate = `{ "status": { "$ref": "#/definitions/types.Status" }, + "subtotal": { + "type": "number" + }, "title": { - "description": "Title holds the value of the \"title\" field.", "type": "string" }, + "total": { + "type": "number" + }, "updated_at": { "type": "string" }, @@ -1952,6 +3225,28 @@ const docTemplate = `{ } } }, + "types.CartLineItemEntityType": { + "type": "string", + "enum": [ + "internship_batch", + "course" + ], + "x-enum-varnames": [ + "CartLineItemEntityTypeInternshipBatch", + "CartLineItemEntityTypeCourse" + ] + }, + "types.CartType": { + "type": "string", + "enum": [ + "onetime", + "default" + ], + "x-enum-varnames": [ + "CartTypeOneTime", + "CartTypeDefault" + ] + }, "types.DiscountType": { "type": "string", "enum": [ @@ -1963,6 +3258,21 @@ const docTemplate = `{ "DiscountTypePercentage" ] }, + "types.InternshipBatchStatus": { + "type": "string", + "enum": [ + "upcoming", + "ongoing", + "completed", + "cancelled" + ], + "x-enum-varnames": [ + "InternshipBatchStatusUpcoming", + "InternshipBatchStatusOngoing", + "InternshipBatchStatusCompleted", + "InternshipBatchStatusCancelled" + ] + }, "types.InternshipLevel": { "type": "string", "enum": [ @@ -2029,11 +3339,13 @@ const docTemplate = `{ "types.UserRole": { "type": "string", "enum": [ + "ADMIN", "STUDENT", "INSTRUCTOR", "ADMIN" ], "x-enum-varnames": [ + "DefaultUserRole", "UserRoleStudent", "UserRoleInstructor", "UserRoleAdmin" diff --git a/docs/swagger/swagger-3-0.json b/docs/swagger/swagger-3-0.json index 8529bee..1277ca5 100644 --- a/docs/swagger/swagger-3-0.json +++ b/docs/swagger/swagger-3-0.json @@ -1,2294 +1 @@ -{ - "openapi": "3.0.1", - "info": { - "title": "CodeGeeky API", - "description": "API for CodeGeeky", - "termsOfService": "http://example.com/terms/", - "contact": { - "name": "API Support", - "email": "support@example.com" - }, - "version": "1.0" - }, - "servers": [ - { - "url": "//localhost:8080/v1" - } - ], - "paths": { - "/auth/signup": { - "post": { - "tags": [ - "Auth" - ], - "summary": "Signup", - "description": "Signup", - "requestBody": { - "description": "Signup request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.SignupRequest" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.SignupResponse" - } - } - } - } - }, - "x-codegen-request-body-name": "signupRequest" - } - }, - "/categories": { - "get": { - "tags": [ - "Category" - ], - "summary": "List categories", - "description": "List categories with optional filtering", - "parameters": [ - { - "name": "category_ids", - "in": "query", - "style": "form", - "explode": false, - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "name": "end_time", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "expand", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "internship_ids", - "in": "query", - "style": "form", - "explode": false, - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "maximum": 1000, - "minimum": 1, - "type": "integer" - } - }, - { - "name": "name", - "in": "query", - "description": "These fields are used to filter categories by name", - "schema": { - "type": "string" - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "minimum": 0, - "type": "integer" - } - }, - { - "name": "order", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - } - }, - { - "name": "sort", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "start_time", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "status", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "published", - "deleted", - "archived", - "inactive", - "pending" - ], - "x-enum-varnames": [ - "StatusPublished", - "StatusDeleted", - "StatusArchived", - "StatusInactive", - "StatusPending" - ] - }, - "x-enum-varnames": [ - "StatusPublished", - "StatusDeleted", - "StatusArchived", - "StatusInactive", - "StatusPending" - ] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.ListCategoryResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - } - }, - "post": { - "tags": [ - "Category" - ], - "summary": "Create a new category", - "description": "Create a new category with the provided details", - "requestBody": { - "description": "Category details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.CreateCategoryRequest" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.CategoryResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "x-codegen-request-body-name": "category" - } - }, - "/categories/{id}": { - "get": { - "tags": [ - "Category" - ], - "summary": "Get a category by ID", - "description": "Get a category by its unique identifier", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Category ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.CategoryResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - } - }, - "put": { - "tags": [ - "Category" - ], - "summary": "Update a category", - "description": "Update a category by its unique identifier", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Category ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Category details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.UpdateCategoryRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.CategoryResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "x-codegen-request-body-name": "category" - }, - "delete": { - "tags": [ - "Category" - ], - "summary": "Delete a category", - "description": "Delete a category by its unique identifier", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Category ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "No Content", - "content": {} - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - } - } - }, - "/discounts": { - "get": { - "tags": [ - "Discount" - ], - "summary": "List discounts", - "description": "List discounts with optional filtering", - "parameters": [ - { - "name": "discount_type", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "flat", - "percentage" - ], - "x-enum-varnames": [ - "DiscountTypeFlat", - "DiscountTypePercentage" - ] - }, - "x-enum-varnames": [ - "DiscountTypeFlat", - "DiscountTypePercentage" - ] - }, - { - "name": "end_time", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "expand", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "is_combinable", - "in": "query", - "schema": { - "type": "boolean" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "maximum": 1000, - "minimum": 1, - "type": "integer" - } - }, - { - "name": "min_order_value", - "in": "query", - "schema": { - "type": "number" - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "minimum": 0, - "type": "integer" - } - }, - { - "name": "order", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - } - }, - { - "name": "sort", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "start_time", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "status", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "published", - "deleted", - "archived", - "inactive", - "pending" - ], - "x-enum-varnames": [ - "StatusPublished", - "StatusDeleted", - "StatusArchived", - "StatusInactive", - "StatusPending" - ] - }, - "x-enum-varnames": [ - "StatusPublished", - "StatusDeleted", - "StatusArchived", - "StatusInactive", - "StatusPending" - ] - }, - { - "name": "valid_from", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "valid_until", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.ListDiscountResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "security": [ - { - "ApiKeyAuth": [] - } - ] - }, - "post": { - "tags": [ - "Discount" - ], - "summary": "Create a new discount", - "description": "Create a new discount with the provided details", - "requestBody": { - "description": "Discount details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.CreateDiscountRequest" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.DiscountResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "security": [ - { - "ApiKeyAuth": [] - } - ], - "x-codegen-request-body-name": "discount" - } - }, - "/discounts/code/{code}": { - "get": { - "tags": [ - "Discount" - ], - "summary": "Get a discount by code", - "description": "Get a discount by its unique code", - "parameters": [ - { - "name": "code", - "in": "path", - "description": "Discount code", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.DiscountResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "security": [ - { - "ApiKeyAuth": [] - } - ] - } - }, - "/discounts/{id}": { - "get": { - "tags": [ - "Discount" - ], - "summary": "Get a discount by ID", - "description": "Get a discount by its unique identifier", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Discount ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.DiscountResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "security": [ - { - "ApiKeyAuth": [] - } - ] - }, - "put": { - "tags": [ - "Discount" - ], - "summary": "Update a discount by ID", - "description": "Update a discount by its unique identifier", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Discount ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Discount details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.UpdateDiscountRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.DiscountResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "security": [ - { - "ApiKeyAuth": [] - } - ], - "x-codegen-request-body-name": "discount" - }, - "delete": { - "tags": [ - "Discount" - ], - "summary": "Delete a discount by ID", - "description": "Delete a discount by its unique identifier", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Discount ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "No Content", - "content": {} - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "security": [ - { - "ApiKeyAuth": [] - } - ] - } - }, - "/internships": { - "get": { - "tags": [ - "Internship" - ], - "summary": "List internships", - "description": "List internships with optional filtering", - "parameters": [ - { - "name": "category_ids", - "in": "query", - "style": "form", - "explode": false, - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "name": "duration_in_weeks", - "in": "query", - "description": "These fields are used to filter internships by duration in weeks", - "schema": { - "maximum": 52, - "minimum": 1, - "type": "integer" - } - }, - { - "name": "end_time", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "expand", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "internship_ids", - "in": "query", - "style": "form", - "explode": false, - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "name": "levels", - "in": "query", - "style": "form", - "explode": false, - "schema": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "beginner", - "intermediate", - "advanced" - ] - } - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "maximum": 1000, - "minimum": 1, - "type": "integer" - } - }, - { - "name": "max_price", - "in": "query", - "description": "These fields are used to filter internships by price", - "schema": { - "type": "number" - } - }, - { - "name": "min_price", - "in": "query", - "schema": { - "type": "number" - } - }, - { - "name": "modes", - "in": "query", - "style": "form", - "explode": false, - "schema": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "remote", - "hybrid", - "onsite" - ] - } - } - }, - { - "name": "name", - "in": "query", - "description": "These fields are used to filter internships by category, level and mode", - "schema": { - "type": "string" - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "minimum": 0, - "type": "integer" - } - }, - { - "name": "order", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - } - }, - { - "name": "sort", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "start_time", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "status", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "published", - "deleted", - "archived", - "inactive", - "pending" - ], - "x-enum-varnames": [ - "StatusPublished", - "StatusDeleted", - "StatusArchived", - "StatusInactive", - "StatusPending" - ] - }, - "x-enum-varnames": [ - "StatusPublished", - "StatusDeleted", - "StatusArchived", - "StatusInactive", - "StatusPending" - ] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.ListInternshipResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - } - }, - "post": { - "tags": [ - "Internship" - ], - "summary": "Create a new internship", - "description": "Create a new internship with the provided details", - "requestBody": { - "description": "Internship details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.CreateInternshipRequest" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.InternshipResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "x-codegen-request-body-name": "internship" - } - }, - "/internships/{id}": { - "get": { - "tags": [ - "Internship" - ], - "summary": "Get an internship by ID", - "description": "Get an internship by its unique identifier", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Internship ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.InternshipResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - } - }, - "put": { - "tags": [ - "Internship" - ], - "summary": "Update an internship", - "description": "Update an internship by its unique identifier", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Internship ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Internship details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.UpdateInternshipRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.InternshipResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "x-codegen-request-body-name": "internship" - }, - "delete": { - "tags": [ - "Internship" - ], - "summary": "Delete an internship", - "description": "Delete an internship by its unique identifier", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Internship ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "No Content", - "content": {} - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - } - } - }, - "/user": { - "put": { - "tags": [ - "User" - ], - "summary": "Update current user", - "description": "Update the current user's information", - "requestBody": { - "description": "Update user request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.UpdateUserRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.MeResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - }, - "x-codegen-request-body-name": "request" - } - }, - "/user/me": { - "get": { - "tags": [ - "User" - ], - "summary": "Get current user", - "description": "Get the current user's information", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dto.MeResponse" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ierr.ErrorResponse" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "dto.CategoryResponse": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "created_by": { - "type": "string" - }, - "description": { - "type": "string", - "description": "Description holds the value of the \"description\" field." - }, - "id": { - "type": "string", - "description": "ID of the ent." - }, - "internships": { - "type": "array", - "description": "internships holds the value of the internships edge.", - "items": { - "$ref": "#/components/schemas/internship.Internship" - } - }, - "lookup_key": { - "type": "string", - "description": "LookupKey holds the value of the \"lookup_key\" field." - }, - "name": { - "type": "string", - "description": "Name holds the value of the \"name\" field." - }, - "status": { - "$ref": "#/components/schemas/types.Status" - }, - "updated_at": { - "type": "string" - }, - "updated_by": { - "type": "string" - } - } - }, - "dto.CreateCategoryRequest": { - "required": [ - "lookup_key", - "name" - ], - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "lookup_key": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "dto.CreateDiscountRequest": { - "required": [ - "code", - "discount_type", - "discount_value" - ], - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "description": { - "type": "string" - }, - "discount_type": { - "$ref": "#/components/schemas/types.DiscountType" - }, - "discount_value": { - "type": "number" - }, - "is_active": { - "type": "boolean" - }, - "is_combinable": { - "type": "boolean" - }, - "max_uses": { - "type": "integer" - }, - "metadata": { - "$ref": "#/components/schemas/types.Metadata" - }, - "min_order_value": { - "type": "number" - }, - "valid_from": { - "type": "string" - }, - "valid_until": { - "type": "string" - } - } - }, - "dto.CreateInternshipRequest": { - "required": [ - "currency", - "description", - "level", - "lookup_key", - "mode", - "price", - "title" - ], - "type": "object", - "properties": { - "benefits": { - "type": "array", - "items": { - "type": "string" - } - }, - "category_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "currency": { - "type": "string" - }, - "description": { - "minLength": 10, - "type": "string" - }, - "duration_in_weeks": { - "minimum": 0, - "type": "integer" - }, - "flat_discount": { - "type": "number" - }, - "learning_outcomes": { - "type": "array", - "items": { - "type": "string" - } - }, - "level": { - "$ref": "#/components/schemas/types.InternshipLevel" - }, - "lookup_key": { - "type": "string" - }, - "mode": { - "$ref": "#/components/schemas/types.InternshipMode" - }, - "percentage_discount": { - "type": "number" - }, - "prerequisites": { - "type": "array", - "items": { - "type": "string" - } - }, - "price": { - "type": "number" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "title": { - "maxLength": 255, - "minLength": 3, - "type": "string" - } - } - }, - "dto.DiscountResponse": { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "created_by": { - "type": "string" - }, - "description": { - "type": "string" - }, - "discount_type": { - "$ref": "#/components/schemas/types.DiscountType" - }, - "discount_value": { - "type": "number" - }, - "id": { - "type": "string" - }, - "is_active": { - "type": "boolean" - }, - "is_combinable": { - "type": "boolean" - }, - "max_uses": { - "type": "integer" - }, - "metadata": { - "$ref": "#/components/schemas/types.Metadata" - }, - "min_order_value": { - "type": "number" - }, - "status": { - "$ref": "#/components/schemas/types.Status" - }, - "updated_at": { - "type": "string" - }, - "updated_by": { - "type": "string" - }, - "valid_from": { - "type": "string" - }, - "valid_until": { - "type": "string" - } - } - }, - "dto.InternshipResponse": { - "type": "object", - "properties": { - "benefits": { - "type": "array", - "description": "Benefits of the internship", - "items": { - "type": "string" - } - }, - "categories": { - "type": "array", - "description": "Categories holds the value of the categories edge.", - "items": { - "$ref": "#/components/schemas/internship.Category" - } - }, - "created_at": { - "type": "string" - }, - "created_by": { - "type": "string" - }, - "currency": { - "type": "string", - "description": "Currency of the internship" - }, - "description": { - "type": "string", - "description": "Description holds the value of the \"description\" field." - }, - "duration_in_weeks": { - "type": "integer", - "description": "Alternative to months for shorter internships" - }, - "flat_discount": { - "type": "number", - "description": "Flat discount on the internship" - }, - "id": { - "type": "string", - "description": "ID of the ent." - }, - "learning_outcomes": { - "type": "array", - "description": "What students will learn in the internship", - "items": { - "type": "string" - } - }, - "level": { - "$ref": "#/components/schemas/types.InternshipLevel" - }, - "lookup_key": { - "type": "string", - "description": "LookupKey holds the value of the \"lookup_key\" field." - }, - "mode": { - "$ref": "#/components/schemas/types.InternshipMode" - }, - "percentage_discount": { - "type": "number", - "description": "Percentage discount on the internship" - }, - "prerequisites": { - "type": "array", - "description": "Prerequisites or recommended knowledge", - "items": { - "type": "string" - } - }, - "price": { - "type": "number", - "description": "Price of the internship" - }, - "skills": { - "type": "array", - "description": "List of required skills", - "items": { - "type": "string" - } - }, - "status": { - "$ref": "#/components/schemas/types.Status" - }, - "title": { - "type": "string", - "description": "Title holds the value of the \"title\" field." - }, - "updated_at": { - "type": "string" - }, - "updated_by": { - "type": "string" - } - } - }, - "dto.ListCategoryResponse": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/dto.CategoryResponse" - } - }, - "pagination": { - "$ref": "#/components/schemas/types.PaginationResponse" - } - } - }, - "dto.ListDiscountResponse": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/dto.DiscountResponse" - } - }, - "pagination": { - "$ref": "#/components/schemas/types.PaginationResponse" - } - } - }, - "dto.ListInternshipResponse": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/dto.InternshipResponse" - } - }, - "pagination": { - "$ref": "#/components/schemas/types.PaginationResponse" - } - } - }, - "dto.MeResponse": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "id": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "role": { - "type": "string" - } - } - }, - "dto.SignupRequest": { - "required": [ - "access_token", - "email", - "full_name", - "role" - ], - "type": "object", - "properties": { - "access_token": { - "type": "string", - "description": "access token" - }, - "email": { - "type": "string", - "description": "basic info" - }, - "full_name": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "role": { - "$ref": "#/components/schemas/types.UserRole" - } - } - }, - "dto.SignupResponse": { - "type": "object", - "properties": { - "access_token": { - "type": "string" - }, - "id": { - "type": "string" - } - } - }, - "dto.UpdateCategoryRequest": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "lookup_key": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "dto.UpdateDiscountRequest": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "is_active": { - "type": "boolean" - }, - "is_combinable": { - "type": "boolean" - }, - "max_uses": { - "type": "integer" - }, - "metadata": { - "$ref": "#/components/schemas/types.Metadata" - }, - "min_order_value": { - "type": "number" - }, - "valid_from": { - "type": "string" - }, - "valid_until": { - "type": "string" - } - } - }, - "dto.UpdateInternshipRequest": { - "type": "object", - "properties": { - "benefits": { - "type": "array", - "items": { - "type": "string" - } - }, - "category_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "currency": { - "type": "string" - }, - "description": { - "minLength": 10, - "type": "string" - }, - "duration_in_weeks": { - "minimum": 0, - "type": "integer" - }, - "flat_discount": { - "type": "number" - }, - "learning_outcomes": { - "type": "array", - "items": { - "type": "string" - } - }, - "level": { - "$ref": "#/components/schemas/types.InternshipLevel" - }, - "lookup_key": { - "type": "string" - }, - "mode": { - "$ref": "#/components/schemas/types.InternshipMode" - }, - "percentage_discount": { - "type": "number" - }, - "prerequisites": { - "type": "array", - "items": { - "type": "string" - } - }, - "price": { - "type": "number" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "title": { - "maxLength": 255, - "minLength": 3, - "type": "string" - } - } - }, - "dto.UpdateUserRequest": { - "type": "object", - "properties": { - "full_name": { - "type": "string" - }, - "phone": { - "type": "string" - } - } - }, - "ierr.ErrorDetail": { - "type": "object", - "properties": { - "details": { - "type": "object", - "additionalProperties": { - "type": "object" - } - }, - "internal_error": { - "type": "string" - }, - "message": { - "type": "string" - } - } - }, - "ierr.ErrorResponse": { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/ierr.ErrorDetail" - }, - "success": { - "type": "boolean" - } - } - }, - "internship.Category": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "created_by": { - "type": "string" - }, - "description": { - "type": "string", - "description": "Description holds the value of the \"description\" field." - }, - "id": { - "type": "string", - "description": "ID of the ent." - }, - "internships": { - "type": "array", - "description": "internships holds the value of the internships edge.", - "items": { - "$ref": "#/components/schemas/internship.Internship" - } - }, - "lookup_key": { - "type": "string", - "description": "LookupKey holds the value of the \"lookup_key\" field." - }, - "name": { - "type": "string", - "description": "Name holds the value of the \"name\" field." - }, - "status": { - "$ref": "#/components/schemas/types.Status" - }, - "updated_at": { - "type": "string" - }, - "updated_by": { - "type": "string" - } - } - }, - "internship.Internship": { - "type": "object", - "properties": { - "benefits": { - "type": "array", - "description": "Benefits of the internship", - "items": { - "type": "string" - } - }, - "categories": { - "type": "array", - "description": "Categories holds the value of the categories edge.", - "items": { - "$ref": "#/components/schemas/internship.Category" - } - }, - "created_at": { - "type": "string" - }, - "created_by": { - "type": "string" - }, - "currency": { - "type": "string", - "description": "Currency of the internship" - }, - "description": { - "type": "string", - "description": "Description holds the value of the \"description\" field." - }, - "duration_in_weeks": { - "type": "integer", - "description": "Alternative to months for shorter internships" - }, - "flat_discount": { - "type": "number", - "description": "Flat discount on the internship" - }, - "id": { - "type": "string", - "description": "ID of the ent." - }, - "learning_outcomes": { - "type": "array", - "description": "What students will learn in the internship", - "items": { - "type": "string" - } - }, - "level": { - "$ref": "#/components/schemas/types.InternshipLevel" - }, - "lookup_key": { - "type": "string", - "description": "LookupKey holds the value of the \"lookup_key\" field." - }, - "mode": { - "$ref": "#/components/schemas/types.InternshipMode" - }, - "percentage_discount": { - "type": "number", - "description": "Percentage discount on the internship" - }, - "prerequisites": { - "type": "array", - "description": "Prerequisites or recommended knowledge", - "items": { - "type": "string" - } - }, - "price": { - "type": "number", - "description": "Price of the internship" - }, - "skills": { - "type": "array", - "description": "List of required skills", - "items": { - "type": "string" - } - }, - "status": { - "$ref": "#/components/schemas/types.Status" - }, - "title": { - "type": "string", - "description": "Title holds the value of the \"title\" field." - }, - "updated_at": { - "type": "string" - }, - "updated_by": { - "type": "string" - } - } - }, - "types.DiscountType": { - "type": "string", - "enum": [ - "flat", - "percentage" - ], - "x-enum-varnames": [ - "DiscountTypeFlat", - "DiscountTypePercentage" - ] - }, - "types.InternshipLevel": { - "type": "string", - "enum": [ - "beginner", - "intermediate", - "advanced" - ], - "x-enum-varnames": [ - "InternshipLevelBeginner", - "InternshipLevelIntermediate", - "InternshipLevelAdvanced" - ] - }, - "types.InternshipMode": { - "type": "string", - "enum": [ - "remote", - "hybrid", - "onsite" - ], - "x-enum-varnames": [ - "InternshipModeRemote", - "InternshipModeHybrid", - "InternshipModeOnsite" - ] - }, - "types.Metadata": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "types.PaginationResponse": { - "type": "object", - "properties": { - "limit": { - "type": "integer" - }, - "offset": { - "type": "integer" - }, - "total": { - "type": "integer" - } - } - }, - "types.Status": { - "type": "string", - "enum": [ - "published", - "deleted", - "archived", - "inactive", - "pending" - ], - "x-enum-varnames": [ - "StatusPublished", - "StatusDeleted", - "StatusArchived", - "StatusInactive", - "StatusPending" - ] - }, - "types.UserRole": { - "type": "string", - "enum": [ - "STUDENT", - "INSTRUCTOR", - "ADMIN" - ], - "x-enum-varnames": [ - "UserRoleStudent", - "UserRoleInstructor", - "UserRoleAdmin" - ] - } - }, - "securitySchemes": { - "Authorization": { - "type": "apiKey", - "description": "Enter the token with the `Bearer ` prefix, e.g. `Bearer `.", - "name": "Authorization", - "in": "header" - } - } - }, - "x-original-swagger-version": "2.0" -} \ No newline at end of file +{"openapi":"3.0.1","info":{"title":"CodeGeeky API","description":"API for CodeGeeky","termsOfService":"http://example.com/terms/","contact":{"name":"API Support","email":"support@example.com"},"version":"1.0"},"servers":[{"url":"//localhost:8080/v1"}],"paths":{"/auth/signup":{"post":{"tags":["Auth"],"summary":"Signup","description":"Signup","requestBody":{"description":"Signup request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.SignupRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.SignupResponse"}}}}},"x-codegen-request-body-name":"signupRequest"}},"/carts":{"get":{"tags":["Cart"],"summary":"List carts","description":"List carts with optional filtering","parameters":[{"name":"cart_type","in":"query","schema":{"type":"string","enum":["onetime","default"],"x-enum-varnames":["CartTypeOneTime","CartTypeDefault"]},"x-enum-varnames":["CartTypeOneTime","CartTypeDefault"]},{"name":"end_time","in":"query","schema":{"type":"string"}},{"name":"entity_id","in":"query","schema":{"type":"string"}},{"name":"entity_type","in":"query","schema":{"type":"string","enum":["internship_batch","course"],"x-enum-varnames":["CartLineItemEntityTypeInternshipBatch","CartLineItemEntityTypeCourse"]},"x-enum-varnames":["CartLineItemEntityTypeInternshipBatch","CartLineItemEntityTypeCourse"]},{"name":"expand","in":"query","schema":{"type":"string"}},{"name":"expires_at","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"maximum":1000,"minimum":1,"type":"integer"}},{"name":"offset","in":"query","schema":{"minimum":0,"type":"integer"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"start_time","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},{"name":"user_id","in":"query","description":"These fields are used to filter carts by user id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ListCartResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]},"post":{"tags":["Cart"],"summary":"Create a new cart","description":"Create a new cart with the provided details","requestBody":{"description":"Cart details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateCartRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CartResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}],"x-codegen-request-body-name":"cart"}},"/carts/default":{"get":{"tags":["Cart"],"summary":"Get user's default cart","description":"Get the default cart for the authenticated user","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CartResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/carts/{id}":{"get":{"tags":["Cart"],"summary":"Get a cart","description":"Get a cart by ID","parameters":[{"name":"id","in":"path","description":"Cart ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CartResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]},"put":{"tags":["Cart"],"summary":"Update a cart","description":"Update a cart by its unique identifier","parameters":[{"name":"id","in":"path","description":"Cart ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Cart details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateCartRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CartResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}],"x-codegen-request-body-name":"cart"},"delete":{"tags":["Cart"],"summary":"Delete a cart","description":"Delete a cart by its unique identifier","parameters":[{"name":"id","in":"path","description":"Cart ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","content":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/carts/{id}/line-items":{"get":{"tags":["Cart"],"summary":"Get cart line items","description":"Get all line items for a specific cart","parameters":[{"name":"id","in":"path","description":"Cart ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cart.CartLineItem"}}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]},"post":{"tags":["Cart"],"summary":"Add line item to cart","description":"Add a new line item to a cart","parameters":[{"name":"id","in":"path","description":"Cart ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Line item details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateCartLineItemRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cart.CartLineItem"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}],"x-codegen-request-body-name":"line_item"}},"/carts/{id}/line-items/{line_item_id}":{"get":{"tags":["Cart"],"summary":"Get line item","description":"Get a specific line item by its ID","parameters":[{"name":"id","in":"path","description":"Cart ID","required":true,"schema":{"type":"string"}},{"name":"line_item_id","in":"path","description":"Line Item ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cart.CartLineItem"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]},"delete":{"tags":["Cart"],"summary":"Remove line item from cart","description":"Remove a line item from a cart","parameters":[{"name":"id","in":"path","description":"Cart ID","required":true,"schema":{"type":"string"}},{"name":"line_item_id","in":"path","description":"Line Item ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","content":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/categories":{"get":{"tags":["Category"],"summary":"List categories","description":"List categories with optional filtering","parameters":[{"name":"category_ids","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"end_time","in":"query","schema":{"type":"string"}},{"name":"expand","in":"query","schema":{"type":"string"}},{"name":"internship_ids","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"limit","in":"query","schema":{"maximum":1000,"minimum":1,"type":"integer"}},{"name":"name","in":"query","description":"These fields are used to filter categories by name","schema":{"type":"string"}},{"name":"offset","in":"query","schema":{"minimum":0,"type":"integer"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"start_time","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ListCategoryResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"post":{"tags":["Category"],"summary":"Create a new category","description":"Create a new category with the provided details","requestBody":{"description":"Category details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateCategoryRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CategoryResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"category"}},"/categories/{id}":{"get":{"tags":["Category"],"summary":"Get a category by ID","description":"Get a category by its unique identifier","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CategoryResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"put":{"tags":["Category"],"summary":"Update a category","description":"Update a category by its unique identifier","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Category details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateCategoryRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CategoryResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"category"},"delete":{"tags":["Category"],"summary":"Delete a category","description":"Delete a category by its unique identifier","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","content":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}}},"/discounts":{"get":{"tags":["Discount"],"summary":"List discounts","description":"List discounts with optional filtering","parameters":[{"name":"codes","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"discount_ids","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"discount_type","in":"query","schema":{"type":"string","enum":["flat","percentage"],"x-enum-varnames":["DiscountTypeFlat","DiscountTypePercentage"]},"x-enum-varnames":["DiscountTypeFlat","DiscountTypePercentage"]},{"name":"end_time","in":"query","schema":{"type":"string"}},{"name":"expand","in":"query","schema":{"type":"string"}},{"name":"is_combinable","in":"query","schema":{"type":"boolean"}},{"name":"limit","in":"query","schema":{"maximum":1000,"minimum":1,"type":"integer"}},{"name":"min_order_value","in":"query","schema":{"type":"number"}},{"name":"offset","in":"query","schema":{"minimum":0,"type":"integer"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"start_time","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},{"name":"valid_from","in":"query","schema":{"type":"string"}},{"name":"valid_until","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ListDiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]},"post":{"tags":["Discount"],"summary":"Create a new discount","description":"Create a new discount with the provided details","requestBody":{"description":"Discount details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateDiscountRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}],"x-codegen-request-body-name":"discount"}},"/discounts/code/{code}":{"get":{"tags":["Discount"],"summary":"Get a discount by code","description":"Get a discount by its unique code","parameters":[{"name":"code","in":"path","description":"Discount code","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/discounts/{id}":{"get":{"tags":["Discount"],"summary":"Get a discount by ID","description":"Get a discount by its unique identifier","parameters":[{"name":"id","in":"path","description":"Discount ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]},"put":{"tags":["Discount"],"summary":"Update a discount by ID","description":"Update a discount by its unique identifier","parameters":[{"name":"id","in":"path","description":"Discount ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Discount details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateDiscountRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DiscountResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}],"x-codegen-request-body-name":"discount"},"delete":{"tags":["Discount"],"summary":"Delete a discount by ID","description":"Delete a discount by its unique identifier","parameters":[{"name":"id","in":"path","description":"Discount ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","content":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/internshipbatches":{"get":{"tags":["InternshipBatch"],"summary":"List internship batches","description":"List internship batches with optional filtering","parameters":[{"name":"batch_status","in":"query","description":"These fields are used to filter internships by start and end date","schema":{"type":"string","enum":["upcoming","ongoing","completed","cancelled"],"x-enum-varnames":["InternshipBatchStatusUpcoming","InternshipBatchStatusOngoing","InternshipBatchStatusCompleted","InternshipBatchStatusCancelled"]},"x-enum-varnames":["InternshipBatchStatusUpcoming","InternshipBatchStatusOngoing","InternshipBatchStatusCompleted","InternshipBatchStatusCancelled"]},{"name":"end_date","in":"query","schema":{"type":"string"}},{"name":"end_time","in":"query","schema":{"type":"string"}},{"name":"expand","in":"query","schema":{"type":"string"}},{"name":"internship_ids","in":"query","description":"These fields are used to filter internships by internship id","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"limit","in":"query","schema":{"maximum":1000,"minimum":1,"type":"integer"}},{"name":"name","in":"query","description":"These fields are used to filter internships by name","schema":{"type":"string"}},{"name":"offset","in":"query","schema":{"minimum":0,"type":"integer"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"start_date","in":"query","schema":{"type":"string"}},{"name":"start_time","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ListInternshipBatchResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"post":{"tags":["InternshipBatch"],"summary":"Create a new internship batch","description":"Create a new internship batch with the provided details","requestBody":{"description":"Internship batch details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateInternshipBatchRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.InternshipBatchResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"batch"}},"/internshipbatches/{id}":{"get":{"tags":["InternshipBatch"],"summary":"Get an internship batch by ID","description":"Get an internship batch by its unique identifier","parameters":[{"name":"id","in":"path","description":"Internship Batch ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.InternshipBatchResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"put":{"tags":["InternshipBatch"],"summary":"Update an internship batch","description":"Update an internship batch by its unique identifier","parameters":[{"name":"id","in":"path","description":"Internship Batch ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Internship batch details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateInternshipBatchRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.InternshipBatchResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"batch"},"delete":{"tags":["InternshipBatch"],"summary":"Delete an internship batch","description":"Delete an internship batch by its unique identifier","parameters":[{"name":"id","in":"path","description":"Internship Batch ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","content":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}}},"/internships":{"get":{"tags":["Internship"],"summary":"List internships","description":"List internships with optional filtering","parameters":[{"name":"category_ids","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"duration_in_weeks","in":"query","description":"These fields are used to filter internships by duration in weeks","schema":{"maximum":52,"minimum":1,"type":"integer"}},{"name":"end_time","in":"query","schema":{"type":"string"}},{"name":"expand","in":"query","schema":{"type":"string"}},{"name":"internship_ids","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string"}}},{"name":"levels","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string","enum":["beginner","intermediate","advanced"]}}},{"name":"limit","in":"query","schema":{"maximum":1000,"minimum":1,"type":"integer"}},{"name":"max_price","in":"query","description":"These fields are used to filter internships by price","schema":{"type":"number"}},{"name":"min_price","in":"query","schema":{"type":"number"}},{"name":"modes","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"type":"string","enum":["remote","hybrid","onsite"]}}},{"name":"name","in":"query","description":"These fields are used to filter internships by category, level and mode","schema":{"type":"string"}},{"name":"offset","in":"query","schema":{"minimum":0,"type":"integer"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"start_time","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ListInternshipResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"post":{"tags":["Internship"],"summary":"Create a new internship","description":"Create a new internship with the provided details","requestBody":{"description":"Internship details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateInternshipRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.InternshipResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"internship"}},"/internships/{id}":{"get":{"tags":["Internship"],"summary":"Get an internship by ID","description":"Get an internship by its unique identifier","parameters":[{"name":"id","in":"path","description":"Internship ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.InternshipResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}},"put":{"tags":["Internship"],"summary":"Update an internship","description":"Update an internship by its unique identifier","parameters":[{"name":"id","in":"path","description":"Internship ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Internship details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateInternshipRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.InternshipResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"internship"},"delete":{"tags":["Internship"],"summary":"Delete an internship","description":"Delete an internship by its unique identifier","parameters":[{"name":"id","in":"path","description":"Internship ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","content":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}}},"/user":{"put":{"tags":["User"],"summary":"Update current user","description":"Update the current user's information","requestBody":{"description":"Update user request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateUserRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.MeResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}},"x-codegen-request-body-name":"request"}},"/user/me":{"get":{"tags":["User"],"summary":"Get current user","description":"Get the current user's information","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.MeResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ierr.ErrorResponse"}}}}}}}},"components":{"schemas":{"cart.CartLineItem":{"type":"object","properties":{"cart_id":{"type":"string"},"created_at":{"type":"string"},"created_by":{"type":"string"},"discount_amount":{"type":"number"},"entity_id":{"type":"string"},"entity_type":{"$ref":"#/components/schemas/types.CartLineItemEntityType"},"id":{"type":"string"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"per_unit_price":{"type":"number"},"quantity":{"type":"integer"},"status":{"$ref":"#/components/schemas/types.Status"},"subtotal":{"type":"number"},"tax_amount":{"type":"number"},"total":{"type":"number"},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"dto.CartResponse":{"type":"object","properties":{"created_at":{"type":"string"},"created_by":{"type":"string"},"discount_amount":{"type":"number"},"expires_at":{"type":"string"},"id":{"type":"string"},"line_items":{"type":"array","items":{"$ref":"#/components/schemas/cart.CartLineItem"}},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"status":{"$ref":"#/components/schemas/types.Status"},"subtotal":{"type":"number"},"tax_amount":{"type":"number"},"total":{"type":"number"},"type":{"$ref":"#/components/schemas/types.CartType"},"updated_at":{"type":"string"},"updated_by":{"type":"string"},"user_id":{"type":"string"}}},"dto.CategoryResponse":{"type":"object","properties":{"created_at":{"type":"string"},"created_by":{"type":"string"},"description":{"type":"string","description":"Description holds the value of the \"description\" field."},"id":{"type":"string","description":"ID of the ent."},"internships":{"type":"array","description":"internships holds the value of the internships edge.","items":{"$ref":"#/components/schemas/internship.Internship"}},"lookup_key":{"type":"string","description":"LookupKey holds the value of the \"lookup_key\" field."},"name":{"type":"string","description":"Name holds the value of the \"name\" field."},"status":{"$ref":"#/components/schemas/types.Status"},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"dto.CreateCartLineItemRequest":{"required":["entity_id","entity_type","quantity"],"type":"object","properties":{"cart_id":{"type":"string"},"entity_id":{"type":"string"},"entity_type":{"$ref":"#/components/schemas/types.CartLineItemEntityType"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"quantity":{"minimum":1,"type":"integer"}}},"dto.CreateCartRequest":{"required":["type"],"type":"object","properties":{"expires_at":{"type":"string"},"line_items":{"type":"array","items":{"$ref":"#/components/schemas/dto.CreateCartLineItemRequest"}},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"type":{"$ref":"#/components/schemas/types.CartType"}}},"dto.CreateCategoryRequest":{"required":["lookup_key","name"],"type":"object","properties":{"description":{"type":"string"},"lookup_key":{"type":"string"},"name":{"type":"string"}}},"dto.CreateDiscountRequest":{"required":["code","discount_type","discount_value"],"type":"object","properties":{"code":{"type":"string"},"description":{"type":"string"},"discount_type":{"$ref":"#/components/schemas/types.DiscountType"},"discount_value":{"type":"number"},"is_active":{"type":"boolean"},"is_combinable":{"type":"boolean"},"max_uses":{"type":"integer"},"metadata":{"$ref":"#/components/schemas/types.Metadata"},"min_order_value":{"type":"number"},"valid_from":{"type":"string"},"valid_until":{"type":"string"}}},"dto.CreateInternshipBatchRequest":{"required":["internship_id","name"],"type":"object","properties":{"batch_status":{"$ref":"#/components/schemas/types.InternshipBatchStatus"},"description":{"type":"string"},"end_date":{"type":"string"},"internship_id":{"type":"string"},"name":{"maxLength":255,"minLength":3,"type":"string"},"start_date":{"type":"string"}}},"dto.CreateInternshipRequest":{"required":["currency","description","level","lookup_key","mode","price","title"],"type":"object","properties":{"benefits":{"type":"array","items":{"type":"string"}},"category_ids":{"type":"array","items":{"type":"string"}},"currency":{"type":"string"},"description":{"minLength":10,"type":"string"},"duration_in_weeks":{"minimum":0,"type":"integer"},"flat_discount":{"type":"number"},"learning_outcomes":{"type":"array","items":{"type":"string"}},"level":{"$ref":"#/components/schemas/types.InternshipLevel"},"lookup_key":{"type":"string"},"mode":{"$ref":"#/components/schemas/types.InternshipMode"},"percentage_discount":{"type":"number"},"prerequisites":{"type":"array","items":{"type":"string"}},"price":{"type":"number"},"skills":{"type":"array","items":{"type":"string"}},"title":{"maxLength":255,"minLength":3,"type":"string"}}},"dto.DiscountResponse":{"type":"object","properties":{"code":{"type":"string"},"created_at":{"type":"string"},"created_by":{"type":"string"},"description":{"type":"string"},"discount_type":{"$ref":"#/components/schemas/types.DiscountType"},"discount_value":{"type":"number"},"id":{"type":"string"},"is_active":{"type":"boolean"},"is_combinable":{"type":"boolean"},"max_uses":{"type":"integer"},"metadata":{"$ref":"#/components/schemas/types.Metadata"},"min_order_value":{"type":"number"},"status":{"$ref":"#/components/schemas/types.Status"},"updated_at":{"type":"string"},"updated_by":{"type":"string"},"valid_from":{"type":"string"},"valid_until":{"type":"string"}}},"dto.InternshipBatchResponse":{"type":"object","properties":{"batch_status":{"$ref":"#/components/schemas/types.InternshipBatchStatus"},"created_at":{"type":"string"},"created_by":{"type":"string"},"description":{"type":"string"},"end_date":{"type":"string"},"id":{"type":"string"},"internship_id":{"type":"string"},"metadata":{"$ref":"#/components/schemas/types.Metadata"},"name":{"type":"string"},"start_date":{"type":"string"},"status":{"$ref":"#/components/schemas/types.Status"},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"dto.InternshipResponse":{"type":"object","properties":{"benefits":{"type":"array","items":{"type":"string"}},"categories":{"type":"array","items":{"$ref":"#/components/schemas/internship.Category"}},"created_at":{"type":"string"},"created_by":{"type":"string"},"currency":{"type":"string"},"description":{"type":"string"},"duration_in_weeks":{"type":"integer"},"flat_discount":{"type":"number"},"id":{"type":"string"},"learning_outcomes":{"type":"array","items":{"type":"string"}},"level":{"$ref":"#/components/schemas/types.InternshipLevel"},"lookup_key":{"type":"string"},"mode":{"$ref":"#/components/schemas/types.InternshipMode"},"percentage_discount":{"type":"number"},"prerequisites":{"type":"array","items":{"type":"string"}},"price":{"type":"number"},"skills":{"type":"array","items":{"type":"string"}},"status":{"$ref":"#/components/schemas/types.Status"},"subtotal":{"type":"number"},"title":{"type":"string"},"total":{"type":"number"},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"dto.ListCartResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/dto.CartResponse"}},"pagination":{"$ref":"#/components/schemas/types.PaginationResponse"}}},"dto.ListCategoryResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/dto.CategoryResponse"}},"pagination":{"$ref":"#/components/schemas/types.PaginationResponse"}}},"dto.ListDiscountResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/dto.DiscountResponse"}},"pagination":{"$ref":"#/components/schemas/types.PaginationResponse"}}},"dto.ListInternshipBatchResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/dto.InternshipBatchResponse"}},"pagination":{"$ref":"#/components/schemas/types.PaginationResponse"}}},"dto.ListInternshipResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/dto.InternshipResponse"}},"pagination":{"$ref":"#/components/schemas/types.PaginationResponse"}}},"dto.MeResponse":{"type":"object","properties":{"email":{"type":"string"},"full_name":{"type":"string"},"id":{"type":"string"},"phone":{"type":"string"},"role":{"type":"string"}}},"dto.SignupRequest":{"required":["access_token","email","full_name","role"],"type":"object","properties":{"access_token":{"type":"string","description":"access token"},"email":{"type":"string","description":"basic info"},"full_name":{"type":"string"},"phone":{"type":"string"},"role":{"$ref":"#/components/schemas/types.UserRole"}}},"dto.SignupResponse":{"type":"object","properties":{"access_token":{"type":"string"},"id":{"type":"string"}}},"dto.UpdateCartRequest":{"type":"object","properties":{"expires_at":{"type":"string"},"id":{"type":"string"},"line_items":{"type":"array","items":{"$ref":"#/components/schemas/dto.CreateCartLineItemRequest"}},"metadata":{"type":"object","additionalProperties":{"type":"string"}}}},"dto.UpdateCategoryRequest":{"type":"object","properties":{"description":{"type":"string"},"lookup_key":{"type":"string"},"name":{"type":"string"}}},"dto.UpdateDiscountRequest":{"type":"object","properties":{"description":{"type":"string"},"is_active":{"type":"boolean"},"is_combinable":{"type":"boolean"},"max_uses":{"type":"integer"},"metadata":{"$ref":"#/components/schemas/types.Metadata"},"min_order_value":{"type":"number"},"valid_from":{"type":"string"},"valid_until":{"type":"string"}}},"dto.UpdateInternshipBatchRequest":{"type":"object","properties":{"batch_status":{"$ref":"#/components/schemas/types.InternshipBatchStatus"},"description":{"type":"string"},"end_date":{"type":"string"},"name":{"maxLength":255,"minLength":3,"type":"string"},"start_date":{"type":"string"}}},"dto.UpdateInternshipRequest":{"type":"object","properties":{"benefits":{"type":"array","items":{"type":"string"}},"category_ids":{"type":"array","items":{"type":"string"}},"currency":{"type":"string"},"description":{"minLength":10,"type":"string"},"duration_in_weeks":{"minimum":0,"type":"integer"},"flat_discount":{"type":"number"},"learning_outcomes":{"type":"array","items":{"type":"string"}},"level":{"$ref":"#/components/schemas/types.InternshipLevel"},"lookup_key":{"type":"string"},"mode":{"$ref":"#/components/schemas/types.InternshipMode"},"percentage_discount":{"type":"number"},"prerequisites":{"type":"array","items":{"type":"string"}},"price":{"type":"number"},"skills":{"type":"array","items":{"type":"string"}},"title":{"maxLength":255,"minLength":3,"type":"string"}}},"dto.UpdateUserRequest":{"type":"object","properties":{"full_name":{"type":"string"},"phone":{"type":"string"}}},"ierr.ErrorDetail":{"type":"object","properties":{"details":{"type":"object","additionalProperties":{"type":"object"}},"internal_error":{"type":"string"},"message":{"type":"string"}}},"ierr.ErrorResponse":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ierr.ErrorDetail"},"success":{"type":"boolean"}}},"internship.Category":{"type":"object","properties":{"created_at":{"type":"string"},"created_by":{"type":"string"},"description":{"type":"string","description":"Description holds the value of the \"description\" field."},"id":{"type":"string","description":"ID of the ent."},"internships":{"type":"array","description":"internships holds the value of the internships edge.","items":{"$ref":"#/components/schemas/internship.Internship"}},"lookup_key":{"type":"string","description":"LookupKey holds the value of the \"lookup_key\" field."},"name":{"type":"string","description":"Name holds the value of the \"name\" field."},"status":{"$ref":"#/components/schemas/types.Status"},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"internship.Internship":{"type":"object","properties":{"benefits":{"type":"array","items":{"type":"string"}},"categories":{"type":"array","items":{"$ref":"#/components/schemas/internship.Category"}},"created_at":{"type":"string"},"created_by":{"type":"string"},"currency":{"type":"string"},"description":{"type":"string"},"duration_in_weeks":{"type":"integer"},"flat_discount":{"type":"number"},"id":{"type":"string"},"learning_outcomes":{"type":"array","items":{"type":"string"}},"level":{"$ref":"#/components/schemas/types.InternshipLevel"},"lookup_key":{"type":"string"},"mode":{"$ref":"#/components/schemas/types.InternshipMode"},"percentage_discount":{"type":"number"},"prerequisites":{"type":"array","items":{"type":"string"}},"price":{"type":"number"},"skills":{"type":"array","items":{"type":"string"}},"status":{"$ref":"#/components/schemas/types.Status"},"subtotal":{"type":"number"},"title":{"type":"string"},"total":{"type":"number"},"updated_at":{"type":"string"},"updated_by":{"type":"string"}}},"types.CartLineItemEntityType":{"type":"string","enum":["internship_batch","course"],"x-enum-varnames":["CartLineItemEntityTypeInternshipBatch","CartLineItemEntityTypeCourse"]},"types.CartType":{"type":"string","enum":["onetime","default"],"x-enum-varnames":["CartTypeOneTime","CartTypeDefault"]},"types.DiscountType":{"type":"string","enum":["flat","percentage"],"x-enum-varnames":["DiscountTypeFlat","DiscountTypePercentage"]},"types.InternshipBatchStatus":{"type":"string","enum":["upcoming","ongoing","completed","cancelled"],"x-enum-varnames":["InternshipBatchStatusUpcoming","InternshipBatchStatusOngoing","InternshipBatchStatusCompleted","InternshipBatchStatusCancelled"]},"types.InternshipLevel":{"type":"string","enum":["beginner","intermediate","advanced"],"x-enum-varnames":["InternshipLevelBeginner","InternshipLevelIntermediate","InternshipLevelAdvanced"]},"types.InternshipMode":{"type":"string","enum":["remote","hybrid","onsite"],"x-enum-varnames":["InternshipModeRemote","InternshipModeHybrid","InternshipModeOnsite"]},"types.Metadata":{"type":"object","additionalProperties":{"type":"string"}},"types.PaginationResponse":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"types.Status":{"type":"string","enum":["published","deleted","archived","inactive","pending"],"x-enum-varnames":["StatusPublished","StatusDeleted","StatusArchived","StatusInactive","StatusPending"]},"types.UserRole":{"type":"string","enum":["ADMIN","STUDENT","INSTRUCTOR","ADMIN"],"x-enum-varnames":["DefaultUserRole","UserRoleStudent","UserRoleInstructor","UserRoleAdmin"]}},"securitySchemes":{"Authorization":{"type":"apiKey","description":"Enter the token with the `Bearer ` prefix, e.g. `Bearer `.","name":"Authorization","in":"header"}}},"x-original-swagger-version":"2.0"} \ No newline at end of file diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index d5c3a7e..3b7b0a4 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -47,9 +47,14 @@ } } }, - "/categories": { + "/carts": { "get": { - "description": "List categories with optional filtering", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List carts with optional filtering", "consumes": [ "application/json" ], @@ -57,17 +62,21 @@ "application/json" ], "tags": [ - "Category" + "Cart" ], - "summary": "List categories", + "summary": "List carts", "parameters": [ { - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "csv", - "name": "category_ids", + "enum": [ + "onetime", + "default" + ], + "type": "string", + "x-enum-varnames": [ + "CartTypeOneTime", + "CartTypeDefault" + ], + "name": "cart_type", "in": "query" }, { @@ -75,18 +84,32 @@ "name": "end_time", "in": "query" }, + { + "type": "string", + "name": "entity_id", + "in": "query" + }, + { + "enum": [ + "internship_batch", + "course" + ], + "type": "string", + "x-enum-varnames": [ + "CartLineItemEntityTypeInternshipBatch", + "CartLineItemEntityTypeCourse" + ], + "name": "entity_type", + "in": "query" + }, { "type": "string", "name": "expand", "in": "query" }, { - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "csv", - "name": "internship_ids", + "type": "string", + "name": "expires_at", "in": "query" }, { @@ -96,12 +119,6 @@ "name": "limit", "in": "query" }, - { - "type": "string", - "description": "These fields are used to filter categories by name", - "name": "name", - "in": "query" - }, { "minimum": 0, "type": "integer", @@ -145,13 +162,19 @@ ], "name": "status", "in": "query" + }, + { + "type": "string", + "description": "These fields are used to filter carts by user id", + "name": "user_id", + "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.ListCategoryResponse" + "$ref": "#/definitions/dto.ListCartResponse" } }, "400": { @@ -169,7 +192,12 @@ } }, "post": { - "description": "Create a new category with the provided details", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new cart with the provided details", "consumes": [ "application/json" ], @@ -177,17 +205,17 @@ "application/json" ], "tags": [ - "Category" + "Cart" ], - "summary": "Create a new category", + "summary": "Create a new cart", "parameters": [ { - "description": "Category details", - "name": "category", + "description": "Cart details", + "name": "cart", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.CreateCategoryRequest" + "$ref": "#/definitions/dto.CreateCartRequest" } } ], @@ -195,7 +223,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/dto.CategoryResponse" + "$ref": "#/definitions/dto.CartResponse" } }, "400": { @@ -213,9 +241,14 @@ } } }, - "/categories/{id}": { + "/carts/default": { "get": { - "description": "Get a category by its unique identifier", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the default cart for the authenticated user", "consumes": [ "application/json" ], @@ -223,13 +256,53 @@ "application/json" ], "tags": [ - "Category" + "Cart" ], - "summary": "Get a category by ID", + "summary": "Get user's default cart", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CartResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/carts/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get a cart by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Get a cart", "parameters": [ { "type": "string", - "description": "Category ID", + "description": "Cart ID", "name": "id", "in": "path", "required": true @@ -239,7 +312,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.CategoryResponse" + "$ref": "#/definitions/dto.CartResponse" } }, "400": { @@ -263,7 +336,12 @@ } }, "put": { - "description": "Update a category by its unique identifier", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update a cart by its unique identifier", "consumes": [ "application/json" ], @@ -271,24 +349,24 @@ "application/json" ], "tags": [ - "Category" + "Cart" ], - "summary": "Update a category", + "summary": "Update a cart", "parameters": [ { "type": "string", - "description": "Category ID", + "description": "Cart ID", "name": "id", "in": "path", "required": true }, { - "description": "Category details", - "name": "category", + "description": "Cart details", + "name": "cart", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.UpdateCategoryRequest" + "$ref": "#/definitions/dto.UpdateCartRequest" } } ], @@ -296,7 +374,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.CategoryResponse" + "$ref": "#/definitions/dto.CartResponse" } }, "400": { @@ -320,7 +398,12 @@ } }, "delete": { - "description": "Delete a category by its unique identifier", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a cart by its unique identifier", "consumes": [ "application/json" ], @@ -328,13 +411,977 @@ "application/json" ], "tags": [ - "Category" + "Cart" ], - "summary": "Delete a category", + "summary": "Delete a cart", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/carts/{id}/line-items": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all line items for a specific cart", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Get cart line items", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/cart.CartLineItem" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add a new line item to a cart", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Add line item to cart", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Line item details", + "name": "line_item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateCartLineItemRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/cart.CartLineItem" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/carts/{id}/line-items/{line_item_id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get a specific line item by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Get line item", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Line Item ID", + "name": "line_item_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/cart.CartLineItem" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Remove a line item from a cart", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cart" + ], + "summary": "Remove line item from cart", + "parameters": [ + { + "type": "string", + "description": "Cart ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Line Item ID", + "name": "line_item_id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/categories": { + "get": { + "description": "List categories with optional filtering", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Category" + ], + "summary": "List categories", + "parameters": [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "category_ids", + "in": "query" + }, + { + "type": "string", + "name": "end_time", + "in": "query" + }, + { + "type": "string", + "name": "expand", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "internship_ids", + "in": "query" + }, + { + "maximum": 1000, + "minimum": 1, + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "These fields are used to filter categories by name", + "name": "name", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "name": "order", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "start_time", + "in": "query" + }, + { + "enum": [ + "published", + "deleted", + "archived", + "inactive", + "pending" + ], + "type": "string", + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ], + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ListCategoryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "post": { + "description": "Create a new category with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Category" + ], + "summary": "Create a new category", + "parameters": [ + { + "description": "Category details", + "name": "category", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateCategoryRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/dto.CategoryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/categories/{id}": { + "get": { + "description": "Get a category by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Category" + ], + "summary": "Get a category by ID", + "parameters": [ + { + "type": "string", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CategoryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "put": { + "description": "Update a category by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Category" + ], + "summary": "Update a category", + "parameters": [ + { + "type": "string", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Category details", + "name": "category", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateCategoryRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CategoryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Delete a category by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Category" + ], + "summary": "Delete a category", + "parameters": [ + { + "type": "string", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/discounts": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List discounts with optional filtering", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discount" + ], + "summary": "List discounts", + "parameters": [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "codes", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "discount_ids", + "in": "query" + }, + { + "enum": [ + "flat", + "percentage" + ], + "type": "string", + "x-enum-varnames": [ + "DiscountTypeFlat", + "DiscountTypePercentage" + ], + "name": "discount_type", + "in": "query" + }, + { + "type": "string", + "name": "end_time", + "in": "query" + }, + { + "type": "string", + "name": "expand", + "in": "query" + }, + { + "type": "boolean", + "name": "is_combinable", + "in": "query" + }, + { + "maximum": 1000, + "minimum": 1, + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "number", + "name": "min_order_value", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "name": "order", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "start_time", + "in": "query" + }, + { + "enum": [ + "published", + "deleted", + "archived", + "inactive", + "pending" + ], + "type": "string", + "x-enum-varnames": [ + "StatusPublished", + "StatusDeleted", + "StatusArchived", + "StatusInactive", + "StatusPending" + ], + "name": "status", + "in": "query" + }, + { + "type": "string", + "name": "valid_from", + "in": "query" + }, + { + "type": "string", + "name": "valid_until", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ListDiscountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new discount with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discount" + ], + "summary": "Create a new discount", + "parameters": [ + { + "description": "Discount details", + "name": "discount", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateDiscountRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/dto.DiscountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/discounts/code/{code}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get a discount by its unique code", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discount" + ], + "summary": "Get a discount by code", + "parameters": [ + { + "type": "string", + "description": "Discount code", + "name": "code", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DiscountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + }, + "/discounts/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get a discount by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discount" + ], + "summary": "Get a discount by ID", + "parameters": [ + { + "type": "string", + "description": "Discount ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DiscountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update a discount by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discount" + ], + "summary": "Update a discount by ID", + "parameters": [ + { + "type": "string", + "description": "Discount ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Discount details", + "name": "discount", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDiscountRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DiscountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a discount by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discount" + ], + "summary": "Delete a discount by ID", "parameters": [ { "type": "string", - "description": "Category ID", + "description": "Discount ID", "name": "id", "in": "path", "required": true @@ -350,12 +1397,6 @@ "$ref": "#/definitions/ierr.ErrorResponse" } }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/ierr.ErrorResponse" - } - }, "500": { "description": "Internal Server Error", "schema": { @@ -365,14 +1406,9 @@ } } }, - "/discounts": { + "/internshipbatches": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "List discounts with optional filtering", + "description": "List internship batches with optional filtering", "consumes": [ "application/json" ], @@ -380,21 +1416,31 @@ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "List discounts", + "summary": "List internship batches", "parameters": [ { "enum": [ - "flat", - "percentage" + "upcoming", + "ongoing", + "completed", + "cancelled" ], "type": "string", "x-enum-varnames": [ - "DiscountTypeFlat", - "DiscountTypePercentage" + "InternshipBatchStatusUpcoming", + "InternshipBatchStatusOngoing", + "InternshipBatchStatusCompleted", + "InternshipBatchStatusCancelled" ], - "name": "discount_type", + "description": "These fields are used to filter internships by start and end date", + "name": "batch_status", + "in": "query" + }, + { + "type": "string", + "name": "end_date", "in": "query" }, { @@ -408,8 +1454,13 @@ "in": "query" }, { - "type": "boolean", - "name": "is_combinable", + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "description": "These fields are used to filter internships by internship id", + "name": "internship_ids", "in": "query" }, { @@ -420,8 +1471,9 @@ "in": "query" }, { - "type": "number", - "name": "min_order_value", + "type": "string", + "description": "These fields are used to filter internships by name", + "name": "name", "in": "query" }, { @@ -444,6 +1496,11 @@ "name": "sort", "in": "query" }, + { + "type": "string", + "name": "start_date", + "in": "query" + }, { "type": "string", "name": "start_time", @@ -467,23 +1524,13 @@ ], "name": "status", "in": "query" - }, - { - "type": "string", - "name": "valid_from", - "in": "query" - }, - { - "type": "string", - "name": "valid_until", - "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.ListDiscountResponse" + "$ref": "#/definitions/dto.ListInternshipBatchResponse" } }, "400": { @@ -501,12 +1548,7 @@ } }, "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create a new discount with the provided details", + "description": "Create a new internship batch with the provided details", "consumes": [ "application/json" ], @@ -514,17 +1556,17 @@ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "Create a new discount", + "summary": "Create a new internship batch", "parameters": [ { - "description": "Discount details", - "name": "discount", + "description": "Internship batch details", + "name": "batch", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.CreateDiscountRequest" + "$ref": "#/definitions/dto.CreateInternshipBatchRequest" } } ], @@ -532,7 +1574,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/dto.DiscountResponse" + "$ref": "#/definitions/dto.InternshipBatchResponse" } }, "400": { @@ -550,14 +1592,9 @@ } } }, - "/discounts/code/{code}": { + "/internshipbatches/{id}": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get a discount by its unique code", + "description": "Get an internship batch by its unique identifier", "consumes": [ "application/json" ], @@ -565,14 +1602,14 @@ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "Get a discount by code", + "summary": "Get an internship batch by ID", "parameters": [ { "type": "string", - "description": "Discount code", - "name": "code", + "description": "Internship Batch ID", + "name": "id", "in": "path", "required": true } @@ -581,7 +1618,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.DiscountResponse" + "$ref": "#/definitions/dto.InternshipBatchResponse" } }, "400": { @@ -590,51 +1627,8 @@ "$ref": "#/definitions/ierr.ErrorResponse" } }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/ierr.ErrorResponse" - } - } - } - } - }, - "/discounts/{id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get a discount by its unique identifier", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Discount" - ], - "summary": "Get a discount by ID", - "parameters": [ - { - "type": "string", - "description": "Discount ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/dto.DiscountResponse" - } - }, - "400": { - "description": "Bad Request", + "404": { + "description": "Not Found", "schema": { "$ref": "#/definitions/ierr.ErrorResponse" } @@ -648,12 +1642,7 @@ } }, "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update a discount by its unique identifier", + "description": "Update an internship batch by its unique identifier", "consumes": [ "application/json" ], @@ -661,24 +1650,24 @@ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "Update a discount by ID", + "summary": "Update an internship batch", "parameters": [ { "type": "string", - "description": "Discount ID", + "description": "Internship Batch ID", "name": "id", "in": "path", "required": true }, { - "description": "Discount details", - "name": "discount", + "description": "Internship batch details", + "name": "batch", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.UpdateDiscountRequest" + "$ref": "#/definitions/dto.UpdateInternshipBatchRequest" } } ], @@ -686,7 +1675,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dto.DiscountResponse" + "$ref": "#/definitions/dto.InternshipBatchResponse" } }, "400": { @@ -695,6 +1684,12 @@ "$ref": "#/definitions/ierr.ErrorResponse" } }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, "500": { "description": "Internal Server Error", "schema": { @@ -704,12 +1699,7 @@ } }, "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete a discount by its unique identifier", + "description": "Delete an internship batch by its unique identifier", "consumes": [ "application/json" ], @@ -717,13 +1707,13 @@ "application/json" ], "tags": [ - "Discount" + "InternshipBatch" ], - "summary": "Delete a discount by ID", + "summary": "Delete an internship batch", "parameters": [ { "type": "string", - "description": "Discount ID", + "description": "Internship Batch ID", "name": "id", "in": "path", "required": true @@ -739,6 +1729,12 @@ "$ref": "#/definitions/ierr.ErrorResponse" } }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, "500": { "description": "Internal Server Error", "schema": { @@ -1156,46 +2152,158 @@ "$ref": "#/definitions/ierr.ErrorResponse" } } - } - } - }, - "/user/me": { - "get": { - "description": "Get the current user's information", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "User" - ], - "summary": "Get current user", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/dto.MeResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/ierr.ErrorResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/ierr.ErrorResponse" - } + } + } + }, + "/user/me": { + "get": { + "description": "Get the current user's information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Get current user", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.MeResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ierr.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "cart.CartLineItem": { + "type": "object", + "properties": { + "cart_id": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "discount_amount": { + "type": "number" + }, + "entity_id": { + "type": "string" + }, + "entity_type": { + "$ref": "#/definitions/types.CartLineItemEntityType" + }, + "id": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "per_unit_price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "status": { + "$ref": "#/definitions/types.Status" + }, + "subtotal": { + "type": "number" + }, + "tax_amount": { + "type": "number" + }, + "total": { + "type": "number" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, + "dto.CartResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "discount_amount": { + "type": "number" + }, + "expires_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "line_items": { + "type": "array", + "items": { + "$ref": "#/definitions/cart.CartLineItem" + } + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" } + }, + "status": { + "$ref": "#/definitions/types.Status" + }, + "subtotal": { + "type": "number" + }, + "tax_amount": { + "type": "number" + }, + "total": { + "type": "number" + }, + "type": { + "$ref": "#/definitions/types.CartType" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + }, + "user_id": { + "type": "string" } } - } - }, - "definitions": { + }, "dto.CategoryResponse": { "type": "object", "properties": { @@ -1239,6 +2347,61 @@ } } }, + "dto.CreateCartLineItemRequest": { + "type": "object", + "required": [ + "entity_id", + "entity_type", + "quantity" + ], + "properties": { + "cart_id": { + "type": "string" + }, + "entity_id": { + "type": "string" + }, + "entity_type": { + "$ref": "#/definitions/types.CartLineItemEntityType" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "quantity": { + "type": "integer", + "minimum": 1 + } + } + }, + "dto.CreateCartRequest": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "expires_at": { + "type": "string" + }, + "line_items": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.CreateCartLineItemRequest" + } + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "type": { + "$ref": "#/definitions/types.CartType" + } + } + }, "dto.CreateCategoryRequest": { "type": "object", "required": [ @@ -1300,6 +2463,35 @@ } } }, + "dto.CreateInternshipBatchRequest": { + "type": "object", + "required": [ + "internship_id", + "name" + ], + "properties": { + "batch_status": { + "$ref": "#/definitions/types.InternshipBatchStatus" + }, + "description": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "internship_id": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 3 + }, + "start_date": { + "type": "string" + } + } + }, "dto.CreateInternshipRequest": { "type": "object", "required": [ @@ -1434,18 +2626,60 @@ } } }, + "dto.InternshipBatchResponse": { + "type": "object", + "properties": { + "batch_status": { + "$ref": "#/definitions/types.InternshipBatchStatus" + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "description": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "id": { + "type": "string" + }, + "internship_id": { + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/types.Metadata" + }, + "name": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/types.Status" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, "dto.InternshipResponse": { "type": "object", "properties": { "benefits": { - "description": "Benefits of the internship", "type": "array", "items": { "type": "string" } }, "categories": { - "description": "Categories holds the value of the categories edge.", "type": "array", "items": { "$ref": "#/definitions/internship.Category" @@ -1458,61 +2692,48 @@ "type": "string" }, "currency": { - "description": "Currency of the internship", "type": "string" }, "description": { - "description": "Description holds the value of the \"description\" field.", "type": "string" }, "duration_in_weeks": { - "description": "Alternative to months for shorter internships", "type": "integer" }, "flat_discount": { - "description": "Flat discount on the internship", "type": "number" }, "id": { - "description": "ID of the ent.", "type": "string" }, "learning_outcomes": { - "description": "What students will learn in the internship", "type": "array", "items": { "type": "string" } }, "level": { - "description": "Level of the internship: beginner, intermediate, advanced", "$ref": "#/definitions/types.InternshipLevel" }, "lookup_key": { - "description": "LookupKey holds the value of the \"lookup_key\" field.", "type": "string" }, "mode": { - "description": "Internship mode: remote, hybrid, onsite", "$ref": "#/definitions/types.InternshipMode" }, "percentage_discount": { - "description": "Percentage discount on the internship", "type": "number" }, "prerequisites": { - "description": "Prerequisites or recommended knowledge", "type": "array", "items": { "type": "string" } }, "price": { - "description": "Price of the internship", "type": "number" }, "skills": { - "description": "List of required skills", "type": "array", "items": { "type": "string" @@ -1521,10 +2742,15 @@ "status": { "$ref": "#/definitions/types.Status" }, + "subtotal": { + "type": "number" + }, "title": { - "description": "Title holds the value of the \"title\" field.", "type": "string" }, + "total": { + "type": "number" + }, "updated_at": { "type": "string" }, @@ -1533,6 +2759,20 @@ } } }, + "dto.ListCartResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.CartResponse" + } + }, + "pagination": { + "$ref": "#/definitions/types.PaginationResponse" + } + } + }, "dto.ListCategoryResponse": { "type": "object", "properties": { @@ -1561,6 +2801,20 @@ } } }, + "dto.ListInternshipBatchResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.InternshipBatchResponse" + } + }, + "pagination": { + "$ref": "#/definitions/types.PaginationResponse" + } + } + }, "dto.ListInternshipResponse": { "type": "object", "properties": { @@ -1635,6 +2889,29 @@ } } }, + "dto.UpdateCartRequest": { + "type": "object", + "properties": { + "expires_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "line_items": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.CreateCartLineItemRequest" + } + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "dto.UpdateCategoryRequest": { "type": "object", "properties": { @@ -1678,6 +2955,28 @@ } } }, + "dto.UpdateInternshipBatchRequest": { + "type": "object", + "properties": { + "batch_status": { + "$ref": "#/definitions/types.InternshipBatchStatus" + }, + "description": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 3 + }, + "start_date": { + "type": "string" + } + } + }, "dto.UpdateInternshipRequest": { "type": "object", "properties": { @@ -1831,14 +3130,12 @@ "type": "object", "properties": { "benefits": { - "description": "Benefits of the internship", "type": "array", "items": { "type": "string" } }, "categories": { - "description": "Categories holds the value of the categories edge.", "type": "array", "items": { "$ref": "#/definitions/internship.Category" @@ -1851,61 +3148,48 @@ "type": "string" }, "currency": { - "description": "Currency of the internship", "type": "string" }, "description": { - "description": "Description holds the value of the \"description\" field.", "type": "string" }, "duration_in_weeks": { - "description": "Alternative to months for shorter internships", "type": "integer" }, "flat_discount": { - "description": "Flat discount on the internship", "type": "number" }, "id": { - "description": "ID of the ent.", "type": "string" }, "learning_outcomes": { - "description": "What students will learn in the internship", "type": "array", "items": { "type": "string" } }, "level": { - "description": "Level of the internship: beginner, intermediate, advanced", "$ref": "#/definitions/types.InternshipLevel" }, "lookup_key": { - "description": "LookupKey holds the value of the \"lookup_key\" field.", "type": "string" }, "mode": { - "description": "Internship mode: remote, hybrid, onsite", "$ref": "#/definitions/types.InternshipMode" }, "percentage_discount": { - "description": "Percentage discount on the internship", "type": "number" }, "prerequisites": { - "description": "Prerequisites or recommended knowledge", "type": "array", "items": { "type": "string" } }, "price": { - "description": "Price of the internship", "type": "number" }, "skills": { - "description": "List of required skills", "type": "array", "items": { "type": "string" @@ -1914,10 +3198,15 @@ "status": { "$ref": "#/definitions/types.Status" }, + "subtotal": { + "type": "number" + }, "title": { - "description": "Title holds the value of the \"title\" field.", "type": "string" }, + "total": { + "type": "number" + }, "updated_at": { "type": "string" }, @@ -1926,6 +3215,28 @@ } } }, + "types.CartLineItemEntityType": { + "type": "string", + "enum": [ + "internship_batch", + "course" + ], + "x-enum-varnames": [ + "CartLineItemEntityTypeInternshipBatch", + "CartLineItemEntityTypeCourse" + ] + }, + "types.CartType": { + "type": "string", + "enum": [ + "onetime", + "default" + ], + "x-enum-varnames": [ + "CartTypeOneTime", + "CartTypeDefault" + ] + }, "types.DiscountType": { "type": "string", "enum": [ @@ -1937,6 +3248,21 @@ "DiscountTypePercentage" ] }, + "types.InternshipBatchStatus": { + "type": "string", + "enum": [ + "upcoming", + "ongoing", + "completed", + "cancelled" + ], + "x-enum-varnames": [ + "InternshipBatchStatusUpcoming", + "InternshipBatchStatusOngoing", + "InternshipBatchStatusCompleted", + "InternshipBatchStatusCancelled" + ] + }, "types.InternshipLevel": { "type": "string", "enum": [ @@ -2003,11 +3329,13 @@ "types.UserRole": { "type": "string", "enum": [ + "ADMIN", "STUDENT", "INSTRUCTOR", "ADMIN" ], "x-enum-varnames": [ + "DefaultUserRole", "UserRoleStudent", "UserRoleInstructor", "UserRoleAdmin" diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 0b349a7..c629bba 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -1,5 +1,79 @@ basePath: /v1 definitions: + cart.CartLineItem: + properties: + cart_id: + type: string + created_at: + type: string + created_by: + type: string + discount_amount: + type: number + entity_id: + type: string + entity_type: + $ref: '#/definitions/types.CartLineItemEntityType' + id: + type: string + metadata: + additionalProperties: + type: string + type: object + per_unit_price: + type: number + quantity: + type: integer + status: + $ref: '#/definitions/types.Status' + subtotal: + type: number + tax_amount: + type: number + total: + type: number + updated_at: + type: string + updated_by: + type: string + type: object + dto.CartResponse: + properties: + created_at: + type: string + created_by: + type: string + discount_amount: + type: number + expires_at: + type: string + id: + type: string + line_items: + items: + $ref: '#/definitions/cart.CartLineItem' + type: array + metadata: + additionalProperties: + type: string + type: object + status: + $ref: '#/definitions/types.Status' + subtotal: + type: number + tax_amount: + type: number + total: + type: number + type: + $ref: '#/definitions/types.CartType' + updated_at: + type: string + updated_by: + type: string + user_id: + type: string + type: object dto.CategoryResponse: properties: created_at: @@ -30,6 +104,43 @@ definitions: updated_by: type: string type: object + dto.CreateCartLineItemRequest: + properties: + cart_id: + type: string + entity_id: + type: string + entity_type: + $ref: '#/definitions/types.CartLineItemEntityType' + metadata: + additionalProperties: + type: string + type: object + quantity: + minimum: 1 + type: integer + required: + - entity_id + - entity_type + - quantity + type: object + dto.CreateCartRequest: + properties: + expires_at: + type: string + line_items: + items: + $ref: '#/definitions/dto.CreateCartLineItemRequest' + type: array + metadata: + additionalProperties: + type: string + type: object + type: + $ref: '#/definitions/types.CartType' + required: + - type + type: object dto.CreateCategoryRequest: properties: description: @@ -71,6 +182,26 @@ definitions: - discount_type - discount_value type: object + dto.CreateInternshipBatchRequest: + properties: + batch_status: + $ref: '#/definitions/types.InternshipBatchStatus' + description: + type: string + end_date: + type: string + internship_id: + type: string + name: + maxLength: 255 + minLength: 3 + type: string + start_date: + type: string + required: + - internship_id + - name + type: object dto.CreateInternshipRequest: properties: benefits: @@ -163,15 +294,42 @@ definitions: valid_until: type: string type: object + dto.InternshipBatchResponse: + properties: + batch_status: + $ref: '#/definitions/types.InternshipBatchStatus' + created_at: + type: string + created_by: + type: string + description: + type: string + end_date: + type: string + id: + type: string + internship_id: + type: string + metadata: + $ref: '#/definitions/types.Metadata' + name: + type: string + start_date: + type: string + status: + $ref: '#/definitions/types.Status' + updated_at: + type: string + updated_by: + type: string + type: object dto.InternshipResponse: properties: benefits: - description: Benefits of the internship items: type: string type: array categories: - description: Categories holds the value of the categories edge. items: $ref: '#/definitions/internship.Category' type: array @@ -180,62 +338,59 @@ definitions: created_by: type: string currency: - description: Currency of the internship type: string description: - description: Description holds the value of the "description" field. type: string duration_in_weeks: - description: Alternative to months for shorter internships type: integer flat_discount: - description: Flat discount on the internship type: number id: - description: ID of the ent. type: string learning_outcomes: - description: What students will learn in the internship items: type: string type: array level: - allOf: - - $ref: '#/definitions/types.InternshipLevel' - description: 'Level of the internship: beginner, intermediate, advanced' + $ref: '#/definitions/types.InternshipLevel' lookup_key: - description: LookupKey holds the value of the "lookup_key" field. type: string mode: - allOf: - - $ref: '#/definitions/types.InternshipMode' - description: 'Internship mode: remote, hybrid, onsite' + $ref: '#/definitions/types.InternshipMode' percentage_discount: - description: Percentage discount on the internship type: number prerequisites: - description: Prerequisites or recommended knowledge items: type: string type: array price: - description: Price of the internship type: number skills: - description: List of required skills items: type: string type: array status: $ref: '#/definitions/types.Status' + subtotal: + type: number title: - description: Title holds the value of the "title" field. type: string + total: + type: number updated_at: type: string updated_by: type: string type: object + dto.ListCartResponse: + properties: + items: + items: + $ref: '#/definitions/dto.CartResponse' + type: array + pagination: + $ref: '#/definitions/types.PaginationResponse' + type: object dto.ListCategoryResponse: properties: items: @@ -254,6 +409,15 @@ definitions: pagination: $ref: '#/definitions/types.PaginationResponse' type: object + dto.ListInternshipBatchResponse: + properties: + items: + items: + $ref: '#/definitions/dto.InternshipBatchResponse' + type: array + pagination: + $ref: '#/definitions/types.PaginationResponse' + type: object dto.ListInternshipResponse: properties: items: @@ -305,6 +469,21 @@ definitions: id: type: string type: object + dto.UpdateCartRequest: + properties: + expires_at: + type: string + id: + type: string + line_items: + items: + $ref: '#/definitions/dto.CreateCartLineItemRequest' + type: array + metadata: + additionalProperties: + type: string + type: object + type: object dto.UpdateCategoryRequest: properties: description: @@ -333,6 +512,21 @@ definitions: valid_until: type: string type: object + dto.UpdateInternshipBatchRequest: + properties: + batch_status: + $ref: '#/definitions/types.InternshipBatchStatus' + description: + type: string + end_date: + type: string + name: + maxLength: 255 + minLength: 3 + type: string + start_date: + type: string + type: object dto.UpdateInternshipRequest: properties: benefits: @@ -437,12 +631,10 @@ definitions: internship.Internship: properties: benefits: - description: Benefits of the internship items: type: string type: array categories: - description: Categories holds the value of the categories edge. items: $ref: '#/definitions/internship.Category' type: array @@ -451,62 +643,66 @@ definitions: created_by: type: string currency: - description: Currency of the internship type: string description: - description: Description holds the value of the "description" field. type: string duration_in_weeks: - description: Alternative to months for shorter internships type: integer flat_discount: - description: Flat discount on the internship type: number id: - description: ID of the ent. type: string learning_outcomes: - description: What students will learn in the internship items: type: string type: array level: - allOf: - - $ref: '#/definitions/types.InternshipLevel' - description: 'Level of the internship: beginner, intermediate, advanced' + $ref: '#/definitions/types.InternshipLevel' lookup_key: - description: LookupKey holds the value of the "lookup_key" field. type: string mode: - allOf: - - $ref: '#/definitions/types.InternshipMode' - description: 'Internship mode: remote, hybrid, onsite' + $ref: '#/definitions/types.InternshipMode' percentage_discount: - description: Percentage discount on the internship type: number prerequisites: - description: Prerequisites or recommended knowledge items: type: string type: array price: - description: Price of the internship type: number skills: - description: List of required skills items: type: string type: array status: $ref: '#/definitions/types.Status' + subtotal: + type: number title: - description: Title holds the value of the "title" field. type: string + total: + type: number updated_at: type: string updated_by: type: string type: object + types.CartLineItemEntityType: + enum: + - internship_batch + - course + type: string + x-enum-varnames: + - CartLineItemEntityTypeInternshipBatch + - CartLineItemEntityTypeCourse + types.CartType: + enum: + - onetime + - default + type: string + x-enum-varnames: + - CartTypeOneTime + - CartTypeDefault types.DiscountType: enum: - flat @@ -515,6 +711,18 @@ definitions: x-enum-varnames: - DiscountTypeFlat - DiscountTypePercentage + types.InternshipBatchStatus: + enum: + - upcoming + - ongoing + - completed + - cancelled + type: string + x-enum-varnames: + - InternshipBatchStatusUpcoming + - InternshipBatchStatusOngoing + - InternshipBatchStatusCompleted + - InternshipBatchStatusCancelled types.InternshipLevel: enum: - beginner @@ -564,11 +772,13 @@ definitions: - StatusPending types.UserRole: enum: + - ADMIN - STUDENT - INSTRUCTOR - ADMIN type: string x-enum-varnames: + - DefaultUserRole - UserRoleStudent - UserRoleInstructor - UserRoleAdmin @@ -604,39 +814,47 @@ paths: summary: Signup tags: - Auth - /categories: + /carts: get: consumes: - application/json - description: List categories with optional filtering + description: List carts with optional filtering parameters: - - collectionFormat: csv + - enum: + - onetime + - default in: query - items: - type: string - name: category_ids - type: array + name: cart_type + type: string + x-enum-varnames: + - CartTypeOneTime + - CartTypeDefault - in: query name: end_time type: string - in: query - name: expand + name: entity_id type: string - - collectionFormat: csv + - enum: + - internship_batch + - course in: query - items: - type: string - name: internship_ids - type: array + name: entity_type + type: string + x-enum-varnames: + - CartLineItemEntityTypeInternshipBatch + - CartLineItemEntityTypeCourse + - in: query + name: expand + type: string + - in: query + name: expires_at + type: string - in: query maximum: 1000 minimum: 1 name: limit type: integer - - description: These fields are used to filter categories by name - in: query - name: name - type: string - in: query minimum: 0 name: offset @@ -668,13 +886,17 @@ paths: - StatusArchived - StatusInactive - StatusPending + - description: These fields are used to filter carts by user id + in: query + name: user_id + type: string produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/dto.ListCategoryResponse' + $ref: '#/definitions/dto.ListCartResponse' "400": description: Bad Request schema: @@ -683,27 +905,29 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ierr.ErrorResponse' - summary: List categories + security: + - ApiKeyAuth: [] + summary: List carts tags: - - Category + - Cart post: consumes: - application/json - description: Create a new category with the provided details + description: Create a new cart with the provided details parameters: - - description: Category details + - description: Cart details in: body - name: category + name: cart required: true schema: - $ref: '#/definitions/dto.CreateCategoryRequest' + $ref: '#/definitions/dto.CreateCartRequest' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/dto.CategoryResponse' + $ref: '#/definitions/dto.CartResponse' "400": description: Bad Request schema: @@ -712,16 +936,18 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ierr.ErrorResponse' - summary: Create a new category + security: + - ApiKeyAuth: [] + summary: Create a new cart tags: - - Category - /categories/{id}: + - Cart + /carts/{id}: delete: consumes: - application/json - description: Delete a category by its unique identifier + description: Delete a cart by its unique identifier parameters: - - description: Category ID + - description: Cart ID in: path name: id required: true @@ -743,15 +969,17 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ierr.ErrorResponse' - summary: Delete a category + security: + - ApiKeyAuth: [] + summary: Delete a cart tags: - - Category + - Cart get: consumes: - application/json - description: Get a category by its unique identifier + description: Get a cart by ID parameters: - - description: Category ID + - description: Cart ID in: path name: id required: true @@ -762,7 +990,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/dto.CategoryResponse' + $ref: '#/definitions/dto.CartResponse' "400": description: Bad Request schema: @@ -775,32 +1003,34 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ierr.ErrorResponse' - summary: Get a category by ID + security: + - ApiKeyAuth: [] + summary: Get a cart tags: - - Category + - Cart put: consumes: - application/json - description: Update a category by its unique identifier + description: Update a cart by its unique identifier parameters: - - description: Category ID + - description: Cart ID in: path name: id required: true type: string - - description: Category details + - description: Cart details in: body - name: category + name: cart required: true schema: - $ref: '#/definitions/dto.UpdateCategoryRequest' + $ref: '#/definitions/dto.UpdateCartRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/dto.CategoryResponse' + $ref: '#/definitions/dto.CartResponse' "400": description: Bad Request schema: @@ -813,28 +1043,433 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ierr.ErrorResponse' - summary: Update a category + security: + - ApiKeyAuth: [] + summary: Update a cart tags: - - Category - /discounts: + - Cart + /carts/{id}/line-items: get: consumes: - application/json - description: List discounts with optional filtering + description: Get all line items for a specific cart parameters: - - enum: - - flat - - percentage - in: query - name: discount_type - type: string - x-enum-varnames: - - DiscountTypeFlat - - DiscountTypePercentage - - in: query - name: end_time + - description: Cart ID + in: path + name: id + required: true type: string - - in: query + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/cart.CartLineItem' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get cart line items + tags: + - Cart + post: + consumes: + - application/json + description: Add a new line item to a cart + parameters: + - description: Cart ID + in: path + name: id + required: true + type: string + - description: Line item details + in: body + name: line_item + required: true + schema: + $ref: '#/definitions/dto.CreateCartLineItemRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/cart.CartLineItem' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Add line item to cart + tags: + - Cart + /carts/{id}/line-items/{line_item_id}: + delete: + consumes: + - application/json + description: Remove a line item from a cart + parameters: + - description: Cart ID + in: path + name: id + required: true + type: string + - description: Line Item ID + in: path + name: line_item_id + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Remove line item from cart + tags: + - Cart + get: + consumes: + - application/json + description: Get a specific line item by its ID + parameters: + - description: Cart ID + in: path + name: id + required: true + type: string + - description: Line Item ID + in: path + name: line_item_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/cart.CartLineItem' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get line item + tags: + - Cart + /carts/default: + get: + consumes: + - application/json + description: Get the default cart for the authenticated user + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.CartResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get user's default cart + tags: + - Cart + /categories: + get: + consumes: + - application/json + description: List categories with optional filtering + parameters: + - collectionFormat: csv + in: query + items: + type: string + name: category_ids + type: array + - in: query + name: end_time + type: string + - in: query + name: expand + type: string + - collectionFormat: csv + in: query + items: + type: string + name: internship_ids + type: array + - in: query + maximum: 1000 + minimum: 1 + name: limit + type: integer + - description: These fields are used to filter categories by name + in: query + name: name + type: string + - in: query + minimum: 0 + name: offset + type: integer + - enum: + - asc + - desc + in: query + name: order + type: string + - in: query + name: sort + type: string + - in: query + name: start_time + type: string + - enum: + - published + - deleted + - archived + - inactive + - pending + in: query + name: status + type: string + x-enum-varnames: + - StatusPublished + - StatusDeleted + - StatusArchived + - StatusInactive + - StatusPending + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.ListCategoryResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: List categories + tags: + - Category + post: + consumes: + - application/json + description: Create a new category with the provided details + parameters: + - description: Category details + in: body + name: category + required: true + schema: + $ref: '#/definitions/dto.CreateCategoryRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/dto.CategoryResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: Create a new category + tags: + - Category + /categories/{id}: + delete: + consumes: + - application/json + description: Delete a category by its unique identifier + parameters: + - description: Category ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: Delete a category + tags: + - Category + get: + consumes: + - application/json + description: Get a category by its unique identifier + parameters: + - description: Category ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.CategoryResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: Get a category by ID + tags: + - Category + put: + consumes: + - application/json + description: Update a category by its unique identifier + parameters: + - description: Category ID + in: path + name: id + required: true + type: string + - description: Category details + in: body + name: category + required: true + schema: + $ref: '#/definitions/dto.UpdateCategoryRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.CategoryResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: Update a category + tags: + - Category + /discounts: + get: + consumes: + - application/json + description: List discounts with optional filtering + parameters: + - collectionFormat: csv + in: query + items: + type: string + name: codes + type: array + - collectionFormat: csv + in: query + items: + type: string + name: discount_ids + type: array + - enum: + - flat + - percentage + in: query + name: discount_type + type: string + x-enum-varnames: + - DiscountTypeFlat + - DiscountTypePercentage + - in: query + name: end_time + type: string + - in: query name: expand type: string - in: query @@ -1062,6 +1697,234 @@ paths: summary: Get a discount by code tags: - Discount + /internshipbatches: + get: + consumes: + - application/json + description: List internship batches with optional filtering + parameters: + - description: These fields are used to filter internships by start and end + date + enum: + - upcoming + - ongoing + - completed + - cancelled + in: query + name: batch_status + type: string + x-enum-varnames: + - InternshipBatchStatusUpcoming + - InternshipBatchStatusOngoing + - InternshipBatchStatusCompleted + - InternshipBatchStatusCancelled + - in: query + name: end_date + type: string + - in: query + name: end_time + type: string + - in: query + name: expand + type: string + - collectionFormat: csv + description: These fields are used to filter internships by internship id + in: query + items: + type: string + name: internship_ids + type: array + - in: query + maximum: 1000 + minimum: 1 + name: limit + type: integer + - description: These fields are used to filter internships by name + in: query + name: name + type: string + - in: query + minimum: 0 + name: offset + type: integer + - enum: + - asc + - desc + in: query + name: order + type: string + - in: query + name: sort + type: string + - in: query + name: start_date + type: string + - in: query + name: start_time + type: string + - enum: + - published + - deleted + - archived + - inactive + - pending + in: query + name: status + type: string + x-enum-varnames: + - StatusPublished + - StatusDeleted + - StatusArchived + - StatusInactive + - StatusPending + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.ListInternshipBatchResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: List internship batches + tags: + - InternshipBatch + post: + consumes: + - application/json + description: Create a new internship batch with the provided details + parameters: + - description: Internship batch details + in: body + name: batch + required: true + schema: + $ref: '#/definitions/dto.CreateInternshipBatchRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/dto.InternshipBatchResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: Create a new internship batch + tags: + - InternshipBatch + /internshipbatches/{id}: + delete: + consumes: + - application/json + description: Delete an internship batch by its unique identifier + parameters: + - description: Internship Batch ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: Delete an internship batch + tags: + - InternshipBatch + get: + consumes: + - application/json + description: Get an internship batch by its unique identifier + parameters: + - description: Internship Batch ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.InternshipBatchResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: Get an internship batch by ID + tags: + - InternshipBatch + put: + consumes: + - application/json + description: Update an internship batch by its unique identifier + parameters: + - description: Internship Batch ID + in: path + name: id + required: true + type: string + - description: Internship batch details + in: body + name: batch + required: true + schema: + $ref: '#/definitions/dto.UpdateInternshipBatchRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.InternshipBatchResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/ierr.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ierr.ErrorResponse' + summary: Update an internship batch + tags: + - InternshipBatch /internships: get: consumes: diff --git a/internal/api/router.go b/internal/api/router.go index a9fb9de..43fe675 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -11,12 +11,14 @@ import ( ) type Handlers struct { - Health *v1.HealthHandler - Auth *v1.AuthHandler - User *v1.UserHandler - Internship *v1.InternshipHandler - Category *v1.CategoryHandler - Discount *v1.DiscountHandler + Health *v1.HealthHandler + Auth *v1.AuthHandler + User *v1.UserHandler + Internship *v1.InternshipHandler + InternshipBatch *v1.InternshipBatchHandler + Category *v1.CategoryHandler + Discount *v1.DiscountHandler + Cart *v1.CartHandler } func NewRouter(handlers *Handlers, cfg *config.Configuration, logger *logger.Logger) *gin.Engine { @@ -84,5 +86,36 @@ func NewRouter(handlers *Handlers, cfg *config.Configuration, logger *logger.Log v1Discount.PUT("/:id", handlers.Discount.UpdateDiscount) v1Discount.DELETE("/:id", handlers.Discount.DeleteDiscount) } + + // Cart routes + v1Cart := v1Router.Group("/carts") + v1Cart.Use(middleware.AuthenticateMiddleware(cfg, logger)) + { + v1Cart.POST("", handlers.Cart.CreateCart) + v1Cart.GET("/default", handlers.Cart.GetDefaultCart) + v1Cart.GET("", handlers.Cart.ListCarts) + v1Cart.GET("/:id", handlers.Cart.GetCart) + v1Cart.PUT("/:id", handlers.Cart.UpdateCart) + v1Cart.DELETE("/:id", handlers.Cart.DeleteCart) + + // Cart line items routes + v1Cart.GET("/:id/line-items", handlers.Cart.GetCartLineItems) + v1Cart.POST("/:id/line-items", handlers.Cart.AddLineItem) + v1Cart.GET("/:id/line-items/:line_item_id", handlers.Cart.GetLineItem) + v1Cart.DELETE("/:id/line-items/:line_item_id", handlers.Cart.RemoveLineItem) + } + + // Internship Batch routes + v1InternshipBatch := v1Router.Group("/internshipbatches") + { + v1InternshipBatch.GET("", handlers.InternshipBatch.ListInternshipBatches) + v1InternshipBatch.GET("/:id", handlers.InternshipBatch.GetInternshipBatch) + + v1InternshipBatch.Use(middleware.AuthenticateMiddleware(cfg, logger)) + v1InternshipBatch.POST("", handlers.InternshipBatch.CreateInternshipBatch) + v1InternshipBatch.PUT("/:id", handlers.InternshipBatch.UpdateInternshipBatch) + v1InternshipBatch.DELETE("/:id", handlers.InternshipBatch.DeleteInternshipBatch) + } + return router } diff --git a/internal/api/v1/cart.go b/internal/api/v1/cart.go index f2108e8..afa2c1f 100644 --- a/internal/api/v1/cart.go +++ b/internal/api/v1/cart.go @@ -4,8 +4,11 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/omkar273/codegeeky/internal/api/dto" + ierr "github.com/omkar273/codegeeky/internal/errors" "github.com/omkar273/codegeeky/internal/logger" "github.com/omkar273/codegeeky/internal/service" + "github.com/omkar273/codegeeky/internal/types" ) type CartHandler struct { @@ -17,6 +20,34 @@ func NewCartHandler(cartService service.CartService, logger *logger.Logger) *Car return &CartHandler{cartService: cartService, logger: logger} } +// @Summary Create a new cart +// @Description Create a new cart with the provided details +// @Tags Cart +// @Accept json +// @Produce json +// @Param cart body dto.CreateCartRequest true "Cart details" +// @Success 201 {object} dto.CartResponse +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts [post] +// @Security ApiKeyAuth +func (h *CartHandler) CreateCart(c *gin.Context) { + var req dto.CreateCartRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.Error(err) + return + } + + cart, err := h.cartService.CreateCart(c.Request.Context(), &req) + if err != nil { + h.logger.Errorw("Failed to create cart", "error", err) + c.Error(err) + return + } + + c.JSON(http.StatusCreated, cart) +} + // @Summary Get a cart // @Description Get a cart by ID // @Tags Cart @@ -25,15 +56,267 @@ func NewCartHandler(cartService service.CartService, logger *logger.Logger) *Car // @Param id path string true "Cart ID" // @Success 200 {object} dto.CartResponse // @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse // @Failure 500 {object} ierr.ErrorResponse // @Router /carts/{id} [get] // @Security ApiKeyAuth func (h *CartHandler) GetCart(c *gin.Context) { id := c.Param("id") + if id == "" { + c.Error(ierr.NewError("Cart ID is required").Mark(ierr.ErrValidation)) + return + } cart, err := h.cartService.GetCart(c.Request.Context(), id) if err != nil { - h.logger.Error("failed to get cart", "error", err) + h.logger.Errorw("Failed to get cart", "error", err, "cart_id", id) + c.Error(err) + return + } + + c.JSON(http.StatusOK, cart) +} + +// @Summary Update a cart +// @Description Update a cart by its unique identifier +// @Tags Cart +// @Accept json +// @Produce json +// @Param id path string true "Cart ID" +// @Param cart body dto.UpdateCartRequest true "Cart details" +// @Success 200 {object} dto.CartResponse +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts/{id} [put] +// @Security ApiKeyAuth +func (h *CartHandler) UpdateCart(c *gin.Context) { + id := c.Param("id") + if id == "" { + c.Error(ierr.NewError("Cart ID is required").Mark(ierr.ErrValidation)) + return + } + + var req dto.UpdateCartRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.Error(err) + return + } + + cart, err := h.cartService.UpdateCart(c.Request.Context(), id, &req) + if err != nil { + h.logger.Errorw("Failed to update cart", "error", err, "cart_id", id) + c.Error(err) + return + } + + c.JSON(http.StatusOK, cart) +} + +// @Summary Delete a cart +// @Description Delete a cart by its unique identifier +// @Tags Cart +// @Accept json +// @Produce json +// @Param id path string true "Cart ID" +// @Success 204 +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts/{id} [delete] +// @Security ApiKeyAuth +func (h *CartHandler) DeleteCart(c *gin.Context) { + id := c.Param("id") + if id == "" { + c.Error(ierr.NewError("Cart ID is required").Mark(ierr.ErrValidation)) + return + } + + err := h.cartService.DeleteCart(c.Request.Context(), id) + if err != nil { + h.logger.Errorw("Failed to delete cart", "error", err, "cart_id", id) + c.Error(err) + return + } + + c.JSON(http.StatusNoContent, nil) +} + +// @Summary List carts +// @Description List carts with optional filtering +// @Tags Cart +// @Accept json +// @Produce json +// @Param filter query types.CartFilter true "Filter options" +// @Success 200 {object} dto.ListCartResponse +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts [get] +// @Security ApiKeyAuth +func (h *CartHandler) ListCarts(c *gin.Context) { + filter := types.NewCartFilter() + if err := c.ShouldBindQuery(filter); err != nil { + c.Error(err) + return + } + + if err := filter.Validate(); err != nil { + c.Error(err) + return + } + + carts, err := h.cartService.ListCarts(c.Request.Context(), filter) + if err != nil { + h.logger.Errorw("Failed to list carts", "error", err) + c.Error(err) + return + } + + c.JSON(http.StatusOK, carts) +} + +// @Summary Get cart line items +// @Description Get all line items for a specific cart +// @Tags Cart +// @Accept json +// @Produce json +// @Param id path string true "Cart ID" +// @Success 200 {array} cart.CartLineItem +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts/{id}/line-items [get] +// @Security ApiKeyAuth +func (h *CartHandler) GetCartLineItems(c *gin.Context) { + cartID := c.Param("id") + if cartID == "" { + c.Error(ierr.NewError("Cart ID is required").Mark(ierr.ErrValidation)) + return + } + + lineItems, err := h.cartService.GetCartLineItems(c.Request.Context(), cartID) + if err != nil { + h.logger.Errorw("Failed to get cart line items", "error", err, "cart_id", cartID) + c.Error(err) + return + } + + c.JSON(http.StatusOK, lineItems) +} + +// @Summary Add line item to cart +// @Description Add a new line item to a cart +// @Tags Cart +// @Accept json +// @Produce json +// @Param id path string true "Cart ID" +// @Param line_item body dto.CreateCartLineItemRequest true "Line item details" +// @Success 201 {object} cart.CartLineItem +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts/{id}/line-items [post] +// @Security ApiKeyAuth +func (h *CartHandler) AddLineItem(c *gin.Context) { + cartID := c.Param("id") + if cartID == "" { + c.Error(ierr.NewError("Cart ID is required").Mark(ierr.ErrValidation)) + return + } + + var req dto.CreateCartLineItemRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.Error(err) + return + } + + // Set the cart ID from the URL parameter + req.CartID = cartID + + lineItem, err := h.cartService.AddLineItem(c.Request.Context(), &req) + if err != nil { + h.logger.Errorw("Failed to add line item", "error", err, "cart_id", cartID) + c.Error(err) + return + } + + c.JSON(http.StatusCreated, lineItem) +} + +// @Summary Remove line item from cart +// @Description Remove a line item from a cart +// @Tags Cart +// @Accept json +// @Produce json +// @Param id path string true "Cart ID" +// @Param line_item_id path string true "Line Item ID" +// @Success 204 +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts/{id}/line-items/{line_item_id} [delete] +// @Security ApiKeyAuth +func (h *CartHandler) RemoveLineItem(c *gin.Context) { + lineItemID := c.Param("line_item_id") + if lineItemID == "" { + c.Error(ierr.NewError("Line item ID is required").Mark(ierr.ErrValidation)) + return + } + + err := h.cartService.RemoveLineItem(c.Request.Context(), lineItemID) + if err != nil { + h.logger.Errorw("Failed to remove line item", "error", err, "line_item_id", lineItemID) + c.Error(err) + return + } + + c.JSON(http.StatusNoContent, nil) +} + +// @Summary Get line item +// @Description Get a specific line item by its ID +// @Tags Cart +// @Accept json +// @Produce json +// @Param id path string true "Cart ID" +// @Param line_item_id path string true "Line Item ID" +// @Success 200 {object} cart.CartLineItem +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts/{id}/line-items/{line_item_id} [get] +// @Security ApiKeyAuth +func (h *CartHandler) GetLineItem(c *gin.Context) { + lineItemID := c.Param("line_item_id") + if lineItemID == "" { + c.Error(ierr.NewError("Line item ID is required").Mark(ierr.ErrValidation)) + return + } + + lineItem, err := h.cartService.GetLineItem(c.Request.Context(), lineItemID) + if err != nil { + h.logger.Errorw("Failed to get line item", "error", err, "line_item_id", lineItemID) + c.Error(err) + return + } + + c.JSON(http.StatusOK, lineItem) +} + +// @Summary Get user's default cart +// @Description Get the default cart for the authenticated user +// @Tags Cart +// @Accept json +// @Produce json +// @Success 200 {object} dto.CartResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /carts/default [get] +// @Security ApiKeyAuth +func (h *CartHandler) GetDefaultCart(c *gin.Context) { + cart, err := h.cartService.GetUserDefaultCart(c.Request.Context()) + if err != nil { + h.logger.Errorw("Failed to get default cart", "error", err) c.Error(err) return } diff --git a/internal/api/v1/category.go b/internal/api/v1/category.go index ca947aa..999729b 100644 --- a/internal/api/v1/category.go +++ b/internal/api/v1/category.go @@ -170,4 +170,4 @@ func (h *CategoryHandler) ListCategories(c *gin.Context) { } c.JSON(http.StatusOK, categories) -} \ No newline at end of file +} diff --git a/internal/api/v1/internship.go b/internal/api/v1/internship.go index f8f52b0..a18c171 100644 --- a/internal/api/v1/internship.go +++ b/internal/api/v1/internship.go @@ -161,7 +161,7 @@ func (h *InternshipHandler) DeleteInternship(c *gin.Context) { // @Produce json // @Param filter query types.InternshipFilter true "Filter options" // @Success 200 {object} dto.ListInternshipResponse -// @Failure 400 {object} ierr.ErrorResponse +// @Failure 400 {object} ierr.ErrorResponse // @Failure 500 {object} ierr.ErrorResponse // @Router /internships [get] func (h *InternshipHandler) ListInternships(c *gin.Context) { diff --git a/internal/api/v1/internship_batch.go b/internal/api/v1/internship_batch.go new file mode 100644 index 0000000..1ba8d02 --- /dev/null +++ b/internal/api/v1/internship_batch.go @@ -0,0 +1,188 @@ +package v1 + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/omkar273/codegeeky/internal/api/dto" + ierr "github.com/omkar273/codegeeky/internal/errors" + "github.com/omkar273/codegeeky/internal/logger" + "github.com/omkar273/codegeeky/internal/service" + "github.com/omkar273/codegeeky/internal/types" +) + +type InternshipBatchHandler struct { + internshipBatchService service.InternshipBatchService + logger *logger.Logger +} + +func NewInternshipBatchHandler( + internshipBatchService service.InternshipBatchService, + logger *logger.Logger, +) *InternshipBatchHandler { + return &InternshipBatchHandler{ + internshipBatchService: internshipBatchService, + logger: logger, + } +} + +// @Summary Create a new internship batch +// @Description Create a new internship batch with the provided details +// @Tags InternshipBatch +// @Accept json +// @Produce json +// @Param batch body dto.CreateInternshipBatchRequest true "Internship batch details" +// @Success 201 {object} dto.InternshipBatchResponse +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /internshipbatches [post] +func (h *InternshipBatchHandler) CreateInternshipBatch(c *gin.Context) { + var req dto.CreateInternshipBatchRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.Error(err) + return + } + + batch, err := h.internshipBatchService.Create(c.Request.Context(), &req) + if err != nil { + h.logger.Errorw("Failed to create internship batch", "error", err) + c.Error(err) + return + } + + c.JSON(http.StatusCreated, batch) +} + +// @Summary Get an internship batch by ID +// @Description Get an internship batch by its unique identifier +// @Tags InternshipBatch +// @Accept json +// @Produce json +// @Param id path string true "Internship Batch ID" +// @Success 200 {object} dto.InternshipBatchResponse +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /internshipbatches/{id} [get] +func (h *InternshipBatchHandler) GetInternshipBatch(c *gin.Context) { + batchID := c.Param("id") + if batchID == "" { + c.Error(ierr.NewError("Internship batch ID is required"). + WithHint("Internship batch ID is required"). + Mark(ierr.ErrValidation)) + return + } + + batch, err := h.internshipBatchService.Get(c.Request.Context(), batchID) + if err != nil { + h.logger.Errorw("Failed to get internship batch", "error", err, "batch_id", batchID) + c.Error(err) + return + } + + c.JSON(http.StatusOK, batch) +} + +// @Summary Update an internship batch +// @Description Update an internship batch by its unique identifier +// @Tags InternshipBatch +// @Accept json +// @Produce json +// @Param id path string true "Internship Batch ID" +// @Param batch body dto.UpdateInternshipBatchRequest true "Internship batch details" +// @Success 200 {object} dto.InternshipBatchResponse +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /internshipbatches/{id} [put] +func (h *InternshipBatchHandler) UpdateInternshipBatch(c *gin.Context) { + batchID := c.Param("id") + if batchID == "" { + c.Error(ierr.NewError("Internship batch ID is required"). + WithHint("Internship batch ID is required"). + Mark(ierr.ErrValidation)) + return + } + + var req dto.UpdateInternshipBatchRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.Error(err) + return + } + + if err := req.Validate(); err != nil { + c.Error(err) + return + } + + batch, err := h.internshipBatchService.Update(c.Request.Context(), batchID, &req) + if err != nil { + h.logger.Errorw("Failed to update internship batch", "error", err, "batch_id", batchID) + c.Error(err) + return + } + + c.JSON(http.StatusOK, batch) +} + +// @Summary Delete an internship batch +// @Description Delete an internship batch by its unique identifier +// @Tags InternshipBatch +// @Accept json +// @Produce json +// @Param id path string true "Internship Batch ID" +// @Success 204 +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 404 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /internshipbatches/{id} [delete] +func (h *InternshipBatchHandler) DeleteInternshipBatch(c *gin.Context) { + batchID := c.Param("id") + if batchID == "" { + c.Error(ierr.NewError("Internship batch ID is required"). + WithHint("Internship batch ID is required"). + Mark(ierr.ErrValidation)) + return + } + + err := h.internshipBatchService.Delete(c.Request.Context(), batchID) + if err != nil { + h.logger.Errorw("Failed to delete internship batch", "error", err, "batch_id", batchID) + c.Error(err) + return + } + + c.JSON(http.StatusNoContent, nil) +} + +// @Summary List internship batches +// @Description List internship batches with optional filtering +// @Tags InternshipBatch +// @Accept json +// @Produce json +// @Param filter query types.InternshipBatchFilter true "Filter options" +// @Success 200 {object} dto.ListInternshipBatchResponse +// @Failure 400 {object} ierr.ErrorResponse +// @Failure 500 {object} ierr.ErrorResponse +// @Router /internshipbatches [get] +func (h *InternshipBatchHandler) ListInternshipBatches(c *gin.Context) { + filter := types.NewInternshipBatchFilter() + if err := c.ShouldBindQuery(filter); err != nil { + c.Error(err) + return + } + + if err := filter.Validate(); err != nil { + c.Error(err) + return + } + + batches, err := h.internshipBatchService.List(c.Request.Context(), filter) + if err != nil { + h.logger.Errorw("Failed to list internship batches", "error", err) + c.Error(err) + return + } + + c.JSON(http.StatusOK, batches) +} diff --git a/internal/service/cart.go b/internal/service/cart.go index df01a15..1539a8c 100644 --- a/internal/service/cart.go +++ b/internal/service/cart.go @@ -19,6 +19,7 @@ type CartService interface { DeleteCart(ctx context.Context, id string) error ListCarts(ctx context.Context, filter *types.CartFilter) (*dto.ListCartResponse, error) GetCartLineItems(ctx context.Context, cartId string) ([]*domainCart.CartLineItem, error) + GetUserDefaultCart(ctx context.Context) (*dto.CartResponse, error) // line item service AddLineItem(ctx context.Context, req *dto.CreateCartLineItemRequest) (*domainCart.CartLineItem, error) @@ -491,3 +492,31 @@ func (s *cartService) updateCartTotals(ctx context.Context, cartID string) error return s.CartRepo.Update(ctx, cart) } + +// GetUserDefaultCart retrieves the default cart for the authenticated user +func (s *cartService) GetUserDefaultCart(ctx context.Context) (*dto.CartResponse, error) { + userID := types.GetUserID(ctx) + if userID == "" { + return nil, ierr.NewError("user ID not found in context"). + WithHint("Please ensure you are authenticated"). + Mark(ierr.ErrValidation) + } + + cart, err := s.CartRepo.GetUserDefaultCart(ctx, userID) + if err != nil { + return nil, err + } + + if cart == nil { + return nil, ierr.NewError("no default cart found"). + WithHint("User does not have a default cart"). + WithReportableDetails(map[string]any{ + "user_id": userID, + }). + Mark(ierr.ErrNotFound) + } + + return &dto.CartResponse{ + Cart: cart, + }, nil +} diff --git a/internal/testutil/inmemory_cart_store.go b/internal/testutil/inmemory_cart_store.go index 262a0ed..9c131d4 100644 --- a/internal/testutil/inmemory_cart_store.go +++ b/internal/testutil/inmemory_cart_store.go @@ -404,7 +404,7 @@ func (s *InMemoryCartStore) GetUserDefaultCart(ctx context.Context, userID strin c.LineItems = lineItems return c, nil } - } + } // No default cart found return nil, ierr.NewError("user default cart not found"). diff --git a/internal/types/internship.go b/internal/types/internship.go index bb732bc..407483d 100644 --- a/internal/types/internship.go +++ b/internal/types/internship.go @@ -55,8 +55,6 @@ func (l InternshipLevel) Validate() error { return nil } - - type InternshipFilter struct { *QueryFilter *TimeRangeFilter From 1b46ad528b3340b5a8631815605266c9c22ad8c7 Mon Sep 17 00:00:00 2001 From: Omkar sonawane <57660189+omkar273@users.noreply.github.com> Date: Mon, 7 Jul 2025 21:31:27 +0530 Subject: [PATCH 09/10] Update internal/repository/ent/cart.go Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- internal/repository/ent/cart.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/repository/ent/cart.go b/internal/repository/ent/cart.go index 4e6b5f0..0da832e 100644 --- a/internal/repository/ent/cart.go +++ b/internal/repository/ent/cart.go @@ -462,7 +462,7 @@ func (r *cartRepository) DeleteCartLineItem(ctx context.Context, id string) erro _, err := client.CartLineItems.UpdateOneID(id). Where( - cartlineitems.CartID(id), + cartlineitems.ID(id), ). SetStatus(string(types.StatusDeleted)). SetUpdatedAt(time.Now().UTC()). From c65596b54dc26503a7d7124a1bd4c7982a6e83ef Mon Sep 17 00:00:00 2001 From: Omkar sonawane <57660189+omkar273@users.noreply.github.com> Date: Mon, 7 Jul 2025 21:31:39 +0530 Subject: [PATCH 10/10] Update docs/swagger/docs.go Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- docs/swagger/docs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 6ce7357..894fb0c 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -3342,7 +3342,6 @@ const docTemplate = `{ "ADMIN", "STUDENT", "INSTRUCTOR", - "ADMIN" ], "x-enum-varnames": [ "DefaultUserRole",