Protecting Routes

When a user creates a website:

  1. User signs in → gets JWT
  2. JWT middleware:
  3. CreateWebsite handler must read user_id from context
  4. Website record must be saved with that user_id

This is how ownership is enforced.

Correct data flow

JWT → AuthMiddleware →context(user_id)
            ↓
      CreateWebsite handler
            ↓
      WebsiteService
            ↓
      WebsiteRepository → INSERT (user_id)

Step 1 : Extract userID in handler

internal/handlers/websites.go

// 1. Get user_id for authentication
	userID, err := contextutil.GetUserID(c)
	
if err != nil {
		c.JSON(http.StatusUnauthorized, response.APIResponse{
			Success: false,
			Error:   err.Error(),
		})
		return
	}

	// 2. Pass it to service
	website, err := h.websiteService.CreateWebsite(ctx, userID, body.URL)

Step 2 : Service layer accepts userID

internal/service/websites.go

func (s *WebsiteService) CreateWebsite(
	ctx context.Context,
	userID string,
	url string,
) (*domain.Website, error) {

	// 1. Normalized URL
	normalisedUrl := strings.TrimSpace(url)

	if normalisedUrl == "" {
		return nil, domain.ErrInvalidURL
	}

	if strings.Contains(normalisedUrl, "localhost") {
		return nil, domain.ErrInvalidURL
	}

	/* 3. Create domain entity
	- this creates a new domain object n memory
	- & means you are creating a pointer of type domain.Website
	*/
	website := &domain.Website{
		URL:    normalisedUrl,
		UserID: userID,
	}

	// 4. Persist
	// s.Repo.Create : Repository, please save this Website entity to the database.
	if err := s.repo.Create(ctx, website); err != nil {
		return nil, err
	}

	// 5. Return fully populated entity
	return website, nil
}

Step 3 : Repository inserts userID

internal/repository/website_repository_pg.go

func (r *websiteRepository) Create(ctx context.Context, website *domain.Website) error {
	query := `
	INSERT INTO website (user_id, url)
	VALUES ($1, $2) 
	RETURNING id, user_id, url, time_added
	`

	err := r.db.QueryRowxContext(
		ctx,
		query,
		website.UserID,
		website.URL,
	).Scan(&website.ID, &website.UserID, &website.URL, &website.TimeAdded)

	if err != nil {
		/*
			err.(*pq.Error) : err is an error interface, under the hood it might be a pg.Error
			i.e POstgreSQL Error.
				- , ok : if ok == true postgres error , ok == false some other error ( network, context )
				- PostgreSQL uses SQLSTATE codes , 23505 means UNIQUE constraint violation.
				- if pgError.Code == "23505" return "URL already exists"
		*/
		if pqError, ok := err.(*pq.Error); ok {
			if pqError.Code == "23505" {
				return domain.ErrURLAlreadyExists
			}
		}
		return err
	}
	return nil
}

Testing Authentication

1. Test Signup & Signin Endpoints