Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A simple blog system using microservices architecture with go-micro v5.
The project consists of the following microservices:

1. **Users Service**: User management (create, read, update, delete)
2. **Posts Service**: Post management (create, read, delete, list)
2. **Posts Service**: Post management (create, read, delete, list, tag management)
3. **Comments Service**: Comment management (create, read, delete, list)
4. **Web Service**: REST API that uses all other services

Expand All @@ -29,6 +29,14 @@ Just start the web service as shown below, then open http://localhost:42096 in y

Authentication and profile features are available via the UI. The static UI interacts with the REST API provided by the web service.

### Tag Features

The blog allows you to:
- Add tags to posts
- Remove tags from posts
- Filter the feed to show posts with a specific tag
- Browse all available tags

## Getting Started

### Prerequisites
Expand Down Expand Up @@ -129,6 +137,19 @@ The REST API and web interface will be available at http://localhost:42096
- `POST /logout`: Log out the current user
- `GET /users/me`: Get the current session user info

### Tags

- `POST /posts/:id/tags`: Add a tag to a post
```json
{
"tag": "tagname"
}
```
- `DELETE /posts/:id/tags/:tag`: Remove a tag from a post
- `GET /tags`: Get all available tags
- `GET /tags?post_id=:id`: Get tags for a specific post
- `GET /posts/by-tag/:tag`: Get posts with a specific tag

## Project Structure

```
Expand Down
133 changes: 133 additions & 0 deletions posts/handler/posts.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,136 @@ func (h *Handler) List(ctx context.Context, req *pb.ListRequest, res *pb.ListRes
res.Total = 0
return nil
}

func (h *Handler) TagPost(ctx context.Context, req *pb.TagPostRequest, res *pb.TagPostResponse) error {
if req.PostId == "" || req.Tag == "" {
return nil
}

rec, err := postStore.Read("post-" + req.PostId)
if err != nil || len(rec) == 0 {
return nil
}

var post pb.Post
if err := json.Unmarshal(rec[0].Value, &post); err != nil {
return nil
}

// Initialize tags slice if it doesn't exist
if post.Tags == nil {
post.Tags = []string{}
}

// Check if tag already exists for this post
for _, tag := range post.Tags {
if tag == req.Tag {
// Tag already exists, return the post as is
res.Post = &post
return nil
}
}

// Add the new tag
post.Tags = append(post.Tags, req.Tag)
post.UpdatedAt = time.Now().Unix()

// Save the updated post
b, err := json.Marshal(&post)
if err == nil {
_ = postStore.Write(&store.Record{Key: "post-" + post.Id, Value: b})
}

res.Post = &post
return nil
}

func (h *Handler) UntagPost(ctx context.Context, req *pb.UntagPostRequest, res *pb.UntagPostResponse) error {
if req.PostId == "" || req.Tag == "" {
return nil
}

rec, err := postStore.Read("post-" + req.PostId)
if err != nil || len(rec) == 0 {
return nil
}

var post pb.Post
if err := json.Unmarshal(rec[0].Value, &post); err != nil {
return nil
}

if len(post.Tags) == 0 {
res.Post = &post
return nil
}

updatedTags := []string{}
for _, tag := range post.Tags {
if tag != req.Tag {
updatedTags = append(updatedTags, tag)
}
}

// Update the post only if tags were changed
if len(updatedTags) != len(post.Tags) {
post.Tags = updatedTags
post.UpdatedAt = time.Now().Unix()

// Save the updated post
b, err := json.Marshal(&post)
if err == nil {
_ = postStore.Write(&store.Record{Key: "post-" + post.Id, Value: b})
}
}

res.Post = &post
return nil
}

func (h *Handler) ListTags(ctx context.Context, req *pb.ListTagsRequest, res *pb.ListTagsResponse) error {
// If postId is specified, get tags for that specific post
if req.PostId != "" {
rec, err := postStore.Read("post-" + req.PostId)
if err != nil || len(rec) == 0 {
return nil
}

var post pb.Post
if err := json.Unmarshal(rec[0].Value, &post); err != nil {
return nil
}

res.Tags = post.Tags
return nil
}

// Otherwise, collect all unique tags across all posts
allTags := make(map[string]struct{})

rec, err := postStore.Read("post-", store.ReadPrefix())
if err != nil || len(rec) == 0 {
return nil
}

for _, r := range rec {
var post pb.Post
if err := json.Unmarshal(r.Value, &post); err == nil {
for _, tag := range post.Tags {
allTags[tag] = struct{}{}
}
}
}

// Convert map keys to slice
uniqueTags := make([]string, 0, len(allTags))
for tag := range allTags {
uniqueTags = append(uniqueTags, tag)
}

// Sort tags alphabetically for consistent output
sort.Strings(uniqueTags)

res.Tags = uniqueTags
return nil
}
Loading