Skip to content

Commit

Permalink
Merge branch 'main' into ホバー機能
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-20 committed Jun 16, 2024
2 parents 4c37bbb + 4be9afa commit 5475d77
Show file tree
Hide file tree
Showing 16 changed files with 162 additions and 53 deletions.
5 changes: 5 additions & 0 deletions backend/domain/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package domain

import "errors"

var ReactionNotFoundError = errors.New("reaction not found")
1 change: 1 addition & 0 deletions backend/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func Start() {
api.GET("/posts", ph.GetPostsHandler)
api.GET("/posts/:postID", ph.GetPostHandler)

api.DELETE("/posts/:postID/reactions/:reactionID", rh.DeleteReactionHandler)
api.POST("/posts/:postID/reactions/:reactionID", rh.PostReactionHandler)

api.GET("/trend", th.GetTrendHandler)
Expand Down
39 changes: 39 additions & 0 deletions backend/handler/reaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
)

type ReactionRepository interface {
DeleteReaction(ctx context.Context, postID uuid.UUID, reactionID int, userName string) ([]*domain.Reaction, error)
GetReactionsByPostID(ctx context.Context, postID uuid.UUID) ([]*domain.Reaction, error)
GetReactionsByPostIDs(ctx context.Context, postIDs []uuid.UUID) (map[uuid.UUID][]*domain.Reaction, error)
GetReactionCountsGroupedByPostID(ctx context.Context, reactionID *int, since time.Time, until time.Time) ([]*domain.ReactionCount, error)
Expand All @@ -31,6 +32,44 @@ type PostReactionResponse struct {
Count int `json:"count"`
}

type deleteReactionResponse struct {
ID int `json:"id"`
Count int `json:"count"`
}

func (rh *ReactionHandler) DeleteReactionHandler(c echo.Context) error {
ctx := c.Request().Context()

postID, err := uuid.Parse(c.Param("postID"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid post id")
}

reactionID, err := strconv.Atoi(c.Param("reactionID"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid reaction id")
}

username, err := getUserName(c)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "failed to get username")
}

reactions, err := rh.rr.DeleteReaction(ctx, postID, reactionID, username)
if errors.Is(err, domain.ReactionNotFoundError) {
return echo.NewHTTPError(http.StatusBadRequest, "reaction not found")
}
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "failed to delete reaction")
}

response := make([]*deleteReactionResponse, len(reactions))
for i, r := range reactions {
response[i] = &deleteReactionResponse{r.ReactionID, r.Count}
}
return c.JSON(http.StatusOK, response)
}

func (rh *ReactionHandler) PostReactionHandler(c echo.Context) error {
ctx := c.Request().Context()

Expand Down
21 changes: 21 additions & 0 deletions backend/repository/reaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@ type Reaction struct {
CreatedAt time.Time `db:"created_at"`
}

func (rr *ReactionRepository) DeleteReaction(ctx context.Context, postID uuid.UUID, reactionID int, userName string) ([]*domain.Reaction, error) {
res, err := rr.DB.Exec("DELETE FROM posts_reactions WHERE post_id=? AND reaction_id=? AND user_name=?", postID, reactionID, userName)
if err != nil {
return nil, fmt.Errorf("failed to delete reaction: %w", err)
}

rowsAffected, err := res.RowsAffected()
if err != nil {
return nil, fmt.Errorf("failed to get number of rows affected: %w", err)
}
if rowsAffected == 0 {
return nil, domain.ReactionNotFoundError
}

reaction, err := rr.GetReactionsByPostID(ctx, postID)
if err != nil {
return nil, fmt.Errorf("failed to get reactions: %w", err)
}
return reaction, nil
}

func (rr *ReactionRepository) GetReactionsByPostID(ctx context.Context, postID uuid.UUID) ([]*domain.Reaction, error) {
var postReactions []Reaction
err := rr.DB.Select(&postReactions, "SELECT * FROM posts_reactions WHERE post_id = ? ORDER BY created_at DESC", postID)
Expand Down
20 changes: 18 additions & 2 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,25 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>

<link rel="icon" href="/favicon.ico" sizes="32x32" />
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.webmanifest" />

<title>発火村</title>
<meta property="og:title" content="発火村" />
<meta
property="og:description"
content="AIによる文章変換を用いた「絶対に炎上させる」SNS。全ての投稿が炎上していることで、逆に炎上という概念をなくすことができます。"
/>
<meta property="og:type" content="article" />
<meta property="og:url" content="https://hakka-mura.trap.show" />
<meta property="og:locale" content="ja" />
<meta property="og:image" content="https://hakka-mura.trap.show/og.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://hakka-mura.trap.show/og.png" />
</head>
<body>
<div id="app"></div>
Expand Down
Binary file added frontend/public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/public/favicon.ico
Binary file not shown.
Binary file added frontend/public/icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/public/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions frontend/public/manifest.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"icons": [
{ "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
{ "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }
]
}
Binary file added frontend/public/og.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion frontend/src/assets/logo.svg

This file was deleted.

79 changes: 47 additions & 32 deletions frontend/src/components/Post.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,45 +60,60 @@ const vTwemoji = {
</script>

<template>
<div class="post">
<div class="post-author-icon">
<Avatar size="48px" :name="name" />
</div>
<div class="post-content">
<div class="post-header">
<span class="post-author">@{{ name }}</span>
<span class="post-date">{{ dateText }}</span>
<router-link :to="`/posts/${id}/`" class="post-link">
<div class="post">
<div class="post-author-icon">
<Avatar size="48px" :name="name" />
</div>
<div class="post-message-container">
<div class="post-message">
{{ content }}
<div class="post-content">
<div class="post-header">
<span class="post-author">@{{ name }}</span>
<span class="post-date">{{ dateText }}</span>
</div>
<div class="post-message-container">
<div class="post-message">
{{ content }}
</div>
<div v-if="!detail" class="original-message">{{ originalContent }}</div>
<div v-if="detail" class="detail-original-message">{{ originalContent }}</div>
</div>
<div class="post-reactions">
<button
v-for="reaction in copiedReactions"
:key="reaction.id"
class="post-reaction"
:class="{ clicked: reaction.clicked, ripple: newReaction === reaction.id }"
@click="
(e) => {
toggleReaction(reaction);
e.stopPropagation();
e.preventDefault();
}
"
>
<span class="post-reaction-icon" v-twemoji>{{ reactionIcons[reaction.id] }}</span>
<span class="post-reaction-count">{{ reaction.count }}</span>
</button>
</div>
<div v-if="!detail" class="original-message">{{ originalContent }}</div>
<div v-if="detail" class="detail-original-message">{{ originalContent }}</div>
</div>
<div class="post-reactions">
<button
v-for="reaction in copiedReactions"
:key="reaction.id"
class="post-reaction"
:class="{ clicked: reaction.clicked, ripple: newReaction === reaction.id }"
@click="
(e) => {
toggleReaction(reaction);
e.stopPropagation();
e.preventDefault();
}
"
>
<span class="post-reaction-icon" v-twemoji>{{ reactionIcons[reaction.id] }}</span>
<span class="post-reaction-count">{{ reaction.count }}</span>
</button>
</div>
</div>
</div>
</router-link>
</template>

<style lang="scss" scoped>
.post-link {
text-decoration: none;
color: inherit;
&::after {
content: '';
display: block;
width: 100%;
height: 1px;
background-color: var(--dimmed-border-color);
}
}
:global(.twemoji) {
height: 1em;
width: 1em;
Expand Down
18 changes: 8 additions & 10 deletions frontend/src/views/MainView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,14 @@ fetchMore();
<NewPostSection @submit="fetchNew" />
<div class="posts">
<div v-for="post in posts" :key="post.id">
<router-link :to="`/posts/${post.id}`" class="post-link" v-if="post.root_id === post.id">
<Post
:id="post.id"
:content="post.converted_message"
:originalContent="post.original_message"
:date="new Date(post.created_at)"
:name="post.user_name"
:reactions="convertReactions(post.reactions, post.my_reactions)"
/>
</router-link>
<Post
:id="post.id"
:content="post.converted_message"
:date="new Date(post.created_at)"
:name="post.user_name"
:originalContent="post.original_message"
:reactions="convertReactions(post.reactions, post.my_reactions)"
/>
</div>
</div>
<IntersectionObserver @intersect="fetchMore" />
Expand Down
24 changes: 16 additions & 8 deletions frontend/src/views/PostView.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import MainLayout from '@/components/MainLayout.vue';
import { useRoute } from 'vue-router';
import { onBeforeRouteUpdate, useRoute } from 'vue-router';
import { getPost, type GetPostResponse } from '@/features/api';
import { ref } from 'vue';
import Post from '@/components/Post.vue';
Expand All @@ -11,12 +11,16 @@ const route = useRoute();
if (route.params.id == undefined) {
// TODO: error, id not found
}
const id = route.params.id as string;
// const id = route.params.id as string;
const postContent = ref<GetPostResponse>();
const loadPost = () => {
const loadPost = (id: string) => {
getPost(id).then((e) => (postContent.value = e));
};
loadPost();
loadPost(useRoute().params.id as string);
onBeforeRouteUpdate((to) => {
loadPost(to.params.id as string);
});
</script>

<template>
Expand All @@ -31,7 +35,7 @@ loadPost();
:name="ancestor.post.user_name"
:reactions="convertReactions(ancestor.post.reactions, ancestor.post.my_reactions)"
:id="ancestor.post.id"
@react="loadPost"
@react="() => loadPost(useRoute().params.id as string)"
/>
</div>
<hr />
Expand All @@ -43,10 +47,14 @@ loadPost();
:reactions="convertReactions(postContent.reactions, postContent.my_reactions)"
:id="postContent.id"
detail
@react="loadPost"
@react="() => loadPost(useRoute().params.id as string)"
/>
<hr />
<NewPostSection :parent-id="postContent.id" @submit="loadPost" />
<NewPostSection
name=""
:parent-id="postContent.id"
@submit="() => loadPost(useRoute().params.id as string)"
/>
<!-- TODO: -->
<div v-for="child in postContent.children" :key="child.post.id">
<Post
Expand All @@ -56,7 +64,7 @@ loadPost();
:name="child.post.user_name"
:reactions="convertReactions(child.post.reactions, child.post.my_reactions)"
:id="child.post.id"
@react="loadPost"
@react="() => loadPost(useRoute().params.id as string)"
/>
</div>
</div>
Expand Down

0 comments on commit 5475d77

Please sign in to comment.