fix issues
This commit is contained in:
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
**/node_modules
|
||||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM docker.hub.ipao.vip/golang:1.22-alpine as builder
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
RUN go env -w GOPROXY=https://go.hub.ipao.vip,direct && \
|
||||||
|
go env -w GO111MODULE=on && \
|
||||||
|
cd /app && \
|
||||||
|
go mod tidy && \
|
||||||
|
go build -o /app/exporter .
|
||||||
|
|
||||||
|
|
||||||
|
FROM docker.hub.ipao.vip/alpine:3.20
|
||||||
|
|
||||||
|
COPY --from=builder /app/exporter /usr/local/bin/exporter
|
||||||
|
COPY config.yml /root/.exporter.yml
|
||||||
|
|
||||||
|
WORKDIR /root
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/exporter"]
|
||||||
|
CMD [ "serve" ]
|
||||||
7
Makefile
7
Makefile
@@ -3,4 +3,9 @@ model:
|
|||||||
rm -rf ./database
|
rm -rf ./database
|
||||||
jet -dsn=postgresql://postgres:xixi0202@10.1.1.3:5432/telegram_resource?sslmode=disable -path=./database
|
jet -dsn=postgresql://postgres:xixi0202@10.1.1.3:5432/telegram_resource?sslmode=disable -path=./database
|
||||||
|
|
||||||
# gofumpt -w -l -extra ./database
|
# gofumpt -w -l -extra ./database
|
||||||
|
|
||||||
|
.PHONY: docker
|
||||||
|
docker:
|
||||||
|
docker build -t docker-af.hub.ipao.vip/rogeecn/tg-exporter:latest .
|
||||||
|
docker push docker-af.hub.ipao.vip/rogeecn/tg-exporter:latest
|
||||||
18
frontend/dist.go
Normal file
18
frontend/dist.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package frontend
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
// embedded files
|
||||||
|
//
|
||||||
|
//go:embed dist/assets/*
|
||||||
|
var StaticDist embed.FS
|
||||||
|
|
||||||
|
// embedded dist/favicon.ico
|
||||||
|
//
|
||||||
|
//go:embed dist/favicon.ico
|
||||||
|
var Favicon []byte
|
||||||
|
|
||||||
|
// embedded dist/index.html
|
||||||
|
//
|
||||||
|
//go:embed dist/index.html
|
||||||
|
var IndexPage string
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-slate-100 border rounded mb-4 p-2 md:mb-8 md:p-4">
|
<div class="mb-4 md:mb-8 border rounded overflow-hidden" :id="`message-${item.ID}`">
|
||||||
<div v-if="item.Content.length > 0" class="text-wrap font-sans" v-html="processedContent"></div>
|
<div class="bg-slate-100 p-2 md:p-4">
|
||||||
|
<div v-if="item.Content.length > 0" class="text-wrap font-sans" v-html="processedContent"></div>
|
||||||
|
|
||||||
<div v-if="item.Media.length > 0" class="mt-2 md:mt-4">
|
<div v-if="item.Media.length > 0" class="mt-2 md:mt-4">
|
||||||
<div class="medias grid grid-cols-3 gap-2 md:gap-4">
|
<div class="medias grid grid-cols-3 gap-2 md:gap-4">
|
||||||
<template v-for="media in item.Media" :key="media.id">
|
<template v-for="media in item.Media" :key="media.id">
|
||||||
<MediaItem :media="media" :channel="item.ChannelID" />
|
<MediaItem :media="media" :channel="item.ChannelID" />
|
||||||
</template>
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2">
|
||||||
|
<button class="py-2 bg-slate-100 hover:bg-slate-50 text-center">Like</button>
|
||||||
|
<button class="py-2 bg-slate-100 hover:bg-slate-50 text-center text-red-600" @click="delMessage()">Delete</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { deleteMessage } from "@/services/messages";
|
||||||
import { computed, defineComponent } from "vue";
|
import { computed, defineComponent } from "vue";
|
||||||
import MediaItem from "./MediaItem.vue";
|
import MediaItem from "./MediaItem.vue";
|
||||||
|
|
||||||
@@ -21,6 +28,7 @@ function nl2br(str, is_xhtml) {
|
|||||||
return (str + "").replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, "$1" + breakTag + "$2");
|
return (str + "").replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, "$1" + breakTag + "$2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
MediaItem,
|
MediaItem,
|
||||||
@@ -35,9 +43,36 @@ export default defineComponent({
|
|||||||
return nl2br(content, false);
|
return nl2br(content, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const delMessage = async () => {
|
||||||
|
console.log("delMessage")
|
||||||
|
if (!confirm("Are you sure?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemId = props.item.ID
|
||||||
|
|
||||||
|
await deleteMessage(itemId);
|
||||||
|
|
||||||
|
// delete dom id #message-{{ item.ID }}
|
||||||
|
console.log("delete", `message-${itemId}`);
|
||||||
|
const messageEle = document.getElementById(`message-${itemId}`);
|
||||||
|
if (messageEle) {
|
||||||
|
// remove with animation
|
||||||
|
messageEle.style.transition = "height 0.5s";
|
||||||
|
messageEle.style.height = "0px";
|
||||||
|
messageEle.style.border = "0px";
|
||||||
|
messageEle.style.margin = "0px";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
messageEle.remove();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
item: props.item,
|
item: props.item,
|
||||||
processedContent,
|
processedContent,
|
||||||
|
delMessage,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<img :src="photoSrc()" class="max-w-full h-full max-h-full" loading="lazy" decoding="async" @click="openPreview" />
|
<img :src="photoSrc()" @click="openPreview" decoding="async" loading="lazy"
|
||||||
|
class="w-full max-w-full h-full max-h-full" />
|
||||||
<div v-if="isPreviewVisible" class="modal" @click="closePreview">
|
<div v-if="isPreviewVisible" class="modal" @click="closePreview">
|
||||||
<img :src="photoSrc()" alt="Preview" class="preview-image" />
|
<img :src="photoSrc()" alt="Preview" class="preview-image" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,15 +10,20 @@ const router = createRouter({
|
|||||||
component: () => import('@/views/Home.vue'),
|
component: () => import('@/views/Home.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/favorites',
|
path: '/favorites/:offset(\d+)?',
|
||||||
name: 'favorites',
|
name: 'favorite-messages',
|
||||||
component: () => import('@/views/FavoriteMessages.vue'),
|
component: () => import('@/views/FavoriteMessages.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/channels/:channel/messages',
|
path: '/channels/:channel/messages/:offset(\d+)?',
|
||||||
name: 'channel-messages',
|
name: 'channel-messages',
|
||||||
component: () => import('@/views/ChannelMessages.vue'),
|
component: () => import('@/views/ChannelMessages.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
name: 'NotFound',
|
||||||
|
component: import('@/views/NotFound.vue'),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -36,3 +36,10 @@ export async function getFavoriteMessages(params) {
|
|||||||
const resp = await http.get(`/favorites`, { params });
|
const resp = await http.get(`/favorites`, { params });
|
||||||
return processResponseMessage(resp.data);
|
return processResponseMessage(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteMessage(messageId) {
|
||||||
|
// return mock('messages', processResponseMessage)
|
||||||
|
|
||||||
|
const resp = await http.delete(`/messages/${messageId}`)
|
||||||
|
return resp.data;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,16 +16,31 @@ import ListItem from "@/components/ListItem.vue";
|
|||||||
import { getChannel } from "@/services/channels";
|
import { getChannel } from "@/services/channels";
|
||||||
import { getChannelMessages } from "@/services/messages";
|
import { getChannelMessages } from "@/services/messages";
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const channel = ref({});
|
const channel = ref({});
|
||||||
const messages = ref([]);
|
const messages = ref([]);
|
||||||
|
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
const items = await getChannelMessages(route.params.channel, { offset: messages.value[messages.value.length - 1].ID });
|
// router goto next page
|
||||||
messages.value.push(...items);
|
// offset is last message ID
|
||||||
|
const offset = messages.value[messages.value.length - 1].ID
|
||||||
|
router.push({
|
||||||
|
name: "channel-messages",
|
||||||
|
params: {
|
||||||
|
channel: route.params.channel,
|
||||||
|
offset: offset,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
messages.value = await getChannelMessages(route.params.channel, { offset: offset });
|
||||||
|
console.log("messages", messages.value);
|
||||||
|
// page scroll to top with animation
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -34,7 +49,7 @@ onMounted(async () => {
|
|||||||
console.log("channel", channel.value);
|
console.log("channel", channel.value);
|
||||||
|
|
||||||
// get channel messages
|
// get channel messages
|
||||||
messages.value = await getChannelMessages(route.params.channel);
|
messages.value = await getChannelMessages(route.params.channel, { offset: route.params.offset });
|
||||||
console.log("messages", messages.value);
|
console.log("messages", messages.value);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -17,15 +17,31 @@
|
|||||||
import ListItem from "@/components/ListItem.vue";
|
import ListItem from "@/components/ListItem.vue";
|
||||||
import { getFavoriteMessages } from "@/services/messages";
|
import { getFavoriteMessages } from "@/services/messages";
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const messages = ref([]);
|
const messages = ref([]);
|
||||||
|
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
const items = await getChannelMessages(route.params.channel, { offset: messages.value[messages.value.length - 1].ID });
|
// router goto next page
|
||||||
messages.value.push(...items);
|
// offset is last message ID
|
||||||
|
const offset = messages.value[messages.value.length - 1].ID
|
||||||
|
router.push({
|
||||||
|
name: "favorite-messages",
|
||||||
|
params: {
|
||||||
|
channel: route.params.channel,
|
||||||
|
offset: offset,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
messages.value = await getFavoriteMessages({ offset: offset });
|
||||||
|
console.log("messages", messages.value);
|
||||||
|
// page scroll to top with animation
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
messages.value = await getFavoriteMessages();
|
messages.value = await getFavoriteMessages({ offset: route.params.offset });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
3
frontend/src/views/NotFound.vue
Normal file
3
frontend/src/views/NotFound.vue
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<h1>404 NotFound</h1>
|
||||||
|
</template>
|
||||||
25
home.text
25
home.text
@@ -1,25 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col space-y-3">
|
|
||||||
<ListItem v-for="item in items" :key="item.id" :item="item" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { PostItem } from '@/types'
|
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
import ListItem from '../components/ListItem.vue'
|
|
||||||
import data from '../data'
|
|
||||||
import { } from
|
|
||||||
|
|
||||||
const items = ref<PostItem[]>([])
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
items.value = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
content: "Hello Man\nHow are you doing",
|
|
||||||
medias: data,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"exporter/database/telegram_resource/public/model"
|
"exporter/database/telegram_resource/public/model"
|
||||||
"exporter/database/telegram_resource/public/table"
|
"exporter/database/telegram_resource/public/table"
|
||||||
"exporter/frontend/dist"
|
"exporter/frontend"
|
||||||
|
|
||||||
. "github.com/go-jet/jet/v2/postgres"
|
. "github.com/go-jet/jet/v2/postgres"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -42,21 +42,22 @@ func serveCmd(cmd *cobra.Command, args []string) error {
|
|||||||
app.Static("/medias", "/share/telegram/outputs")
|
app.Static("/medias", "/share/telegram/outputs")
|
||||||
|
|
||||||
app.Use(favicon.New(favicon.Config{
|
app.Use(favicon.New(favicon.Config{
|
||||||
Data: dist.Favicon,
|
Data: frontend.Favicon,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
app.Use("/assets", filesystem.New(filesystem.Config{
|
app.Use("/assets", filesystem.New(filesystem.Config{
|
||||||
Root: http.FS(dist.StaticDist),
|
Root: http.FS(frontend.StaticDist),
|
||||||
PathPrefix: "assets",
|
PathPrefix: "dist/assets",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Initialize default config
|
// Initialize default config
|
||||||
app.Use(recover.New())
|
app.Use(recover.New())
|
||||||
|
|
||||||
app.Get("/", func(c *fiber.Ctx) error {
|
indexFunc := func(c *fiber.Ctx) error {
|
||||||
c.Context().SetContentType("text/html")
|
c.Context().SetContentType("text/html")
|
||||||
return c.SendString(dist.IndexPage)
|
return c.SendString(frontend.IndexPage)
|
||||||
})
|
}
|
||||||
|
app.Get("/", indexFunc)
|
||||||
|
|
||||||
group := app.Group("/api")
|
group := app.Group("/api")
|
||||||
|
|
||||||
@@ -202,5 +203,8 @@ func serveCmd(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// not found route use frontend router
|
||||||
|
// handle 404
|
||||||
|
app.Use(indexFunc)
|
||||||
return app.Listen(fmt.Sprintf(":%d", port))
|
return app.Listen(fmt.Sprintf(":%d", port))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user