парсер
This commit is contained in:
42
tg_parser/internal/adapter/database/create_post.go
Normal file
42
tg_parser/internal/adapter/database/create_post.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"github.com/TelegramExchange/tgex-parser/internal/domain"
|
||||
"github.com/TelegramExchange/tgex-parser/pkg/transaction"
|
||||
)
|
||||
|
||||
func (d *Database) CreatePost(ctx context.Context, post domain.Post) error {
|
||||
query := `INSERT INTO post (id, channel_id, message_id, text)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (channel_id, message_id) DO UPDATE
|
||||
SET text = EXCLUDED.text,
|
||||
updated_at = CURRENT_TIMESTAMP;`
|
||||
|
||||
txOrPool := transaction.TryExtractTX(ctx)
|
||||
|
||||
dto := createPostDTO{
|
||||
ID: pgtype.UUID{Bytes: post.ID, Valid: true},
|
||||
ChannelID: pgtype.UUID{Bytes: post.ChannelID, Valid: true},
|
||||
MessageID: pgtype.Int4{Int32: int32(post.MessageID), Valid: true},
|
||||
Text: pgtype.Text{String: post.Text, Valid: true},
|
||||
}
|
||||
|
||||
_, err := txOrPool.Exec(ctx, query, dto.ID, dto.ChannelID, dto.MessageID, dto.Text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("txOrPool.Exec: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type createPostDTO struct {
|
||||
ID pgtype.UUID
|
||||
ChannelID pgtype.UUID
|
||||
MessageID pgtype.Int4
|
||||
Text pgtype.Text
|
||||
}
|
||||
40
tg_parser/internal/adapter/database/create_views_snapshot.go
Normal file
40
tg_parser/internal/adapter/database/create_views_snapshot.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"github.com/TelegramExchange/tgex-parser/internal/domain"
|
||||
"github.com/TelegramExchange/tgex-parser/pkg/transaction"
|
||||
)
|
||||
|
||||
func (d *Database) CreateViewsSnapshot(ctx context.Context, snapshot domain.ViewsSnapshot) error {
|
||||
query := `INSERT INTO post_views_history (id, views_count, fetched_at, post_id)
|
||||
VALUES ($1, $2, $3, $4);`
|
||||
|
||||
txOrPool := transaction.TryExtractTX(ctx)
|
||||
|
||||
dto := createViewsSnapshotDTO{
|
||||
ID: pgtype.UUID{Bytes: uuid.New(), Valid: true},
|
||||
ViewsCount: pgtype.Int4{Int32: int32(snapshot.ViewsCount), Valid: true},
|
||||
FetchedAt: pgtype.Timestamptz{Time: snapshot.FetchedAt, Valid: true},
|
||||
PostID: pgtype.UUID{Bytes: snapshot.PostID, Valid: true},
|
||||
}
|
||||
|
||||
_, err := txOrPool.Exec(ctx, query, dto.ID, dto.ViewsCount, dto.FetchedAt, dto.PostID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("txOrPool.Exec: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type createViewsSnapshotDTO struct {
|
||||
ID pgtype.UUID
|
||||
ViewsCount pgtype.Int4
|
||||
FetchedAt pgtype.Timestamptz
|
||||
PostID pgtype.UUID
|
||||
}
|
||||
7
tg_parser/internal/adapter/database/database.go
Normal file
7
tg_parser/internal/adapter/database/database.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package database
|
||||
|
||||
type Database struct{}
|
||||
|
||||
func New() *Database {
|
||||
return &Database{}
|
||||
}
|
||||
37
tg_parser/internal/adapter/database/delete_post.go
Normal file
37
tg_parser/internal/adapter/database/delete_post.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"github.com/TelegramExchange/tgex-parser/internal/domain"
|
||||
"github.com/TelegramExchange/tgex-parser/pkg/transaction"
|
||||
)
|
||||
|
||||
func (d *Database) DeletePost(ctx context.Context, p domain.Post) error {
|
||||
query := `UPDATE post
|
||||
SET deleted_from_channel_at = CURRENT_TIMESTAMP,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE channel_id = $1 AND message_id = $2;`
|
||||
|
||||
txOrPool := transaction.TryExtractTX(ctx)
|
||||
|
||||
dto := deletePostDTO{
|
||||
ChannelID: pgtype.UUID{Bytes: p.ChannelID, Valid: true},
|
||||
MessageID: pgtype.Int4{Int32: int32(p.MessageID), Valid: true},
|
||||
}
|
||||
|
||||
_, err := txOrPool.Exec(ctx, query, dto.ChannelID, dto.MessageID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("txOrPool.Exec: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type deletePostDTO struct {
|
||||
ChannelID pgtype.UUID
|
||||
MessageID pgtype.Int4
|
||||
}
|
||||
79
tg_parser/internal/adapter/database/get_channels.go
Normal file
79
tg_parser/internal/adapter/database/get_channels.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/TelegramExchange/tgex-parser/internal/domain"
|
||||
"github.com/TelegramExchange/tgex-parser/pkg/transaction"
|
||||
)
|
||||
|
||||
func (d *Database) GetChannels(ctx context.Context) []domain.Channel {
|
||||
query := `SELECT
|
||||
id,
|
||||
telegram_id,
|
||||
username,
|
||||
title,
|
||||
access_hash,
|
||||
pts
|
||||
FROM channel
|
||||
WHERE deleted_at IS NULL;`
|
||||
|
||||
txOrPool := transaction.TryExtractTX(ctx)
|
||||
|
||||
rows, err := txOrPool.Query(ctx, query)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("txOrPool.Query failed")
|
||||
return []domain.Channel{}
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
channels := make([]domain.Channel, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var dto getChannelsDTO
|
||||
|
||||
err = rows.Scan(dto.destination()...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("rows.Scan failed")
|
||||
return []domain.Channel{}
|
||||
}
|
||||
|
||||
channels = append(channels, dto.toDomain())
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
type getChannelsDTO struct {
|
||||
ID pgtype.UUID
|
||||
TelegramID pgtype.Int8
|
||||
Username pgtype.Text
|
||||
Title pgtype.Text
|
||||
AccessHash pgtype.Int8
|
||||
Pts pgtype.Int4
|
||||
}
|
||||
|
||||
func (dto *getChannelsDTO) destination() []any {
|
||||
return []any{
|
||||
&dto.ID,
|
||||
&dto.TelegramID,
|
||||
&dto.Username,
|
||||
&dto.Title,
|
||||
&dto.AccessHash,
|
||||
&dto.Pts,
|
||||
}
|
||||
}
|
||||
|
||||
func (dto *getChannelsDTO) toDomain() domain.Channel {
|
||||
return domain.Channel{
|
||||
ID: dto.ID.Bytes,
|
||||
TelegramID: domain.NormalizeChatID(dto.TelegramID.Int64),
|
||||
Username: dto.Username.String,
|
||||
Title: dto.Title.String,
|
||||
AccessHash: dto.AccessHash.Int64,
|
||||
Pts: int(dto.Pts.Int32),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"github.com/TelegramExchange/tgex-parser/internal/domain"
|
||||
"github.com/TelegramExchange/tgex-parser/pkg/transaction"
|
||||
)
|
||||
|
||||
func (d *Database) GetChannelsWithTrackedPosts(ctx context.Context) ([]domain.Channel, error) {
|
||||
query := `SELECT DISTINCT
|
||||
c.id,
|
||||
c.telegram_id,
|
||||
c.username,
|
||||
c.title,
|
||||
c.access_hash,
|
||||
c.pts
|
||||
FROM channel c
|
||||
INNER JOIN placement p ON p.placement_channel_id = c.id
|
||||
INNER JOIN post po ON po.id = p.post_id
|
||||
WHERE po.deleted_from_channel_at IS NULL;`
|
||||
|
||||
txOrPool := transaction.TryExtractTX(ctx)
|
||||
|
||||
rows, err := txOrPool.Query(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("txOrPool.Query: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
channels := make([]domain.Channel, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var dto getChannelsWithTrackedPostsDTO
|
||||
|
||||
err = rows.Scan(dto.destination()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rows.Scan: %w", err)
|
||||
}
|
||||
|
||||
channels = append(channels, dto.toDomain())
|
||||
}
|
||||
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
type getChannelsWithTrackedPostsDTO struct {
|
||||
ID pgtype.UUID
|
||||
TelegramID pgtype.Int8
|
||||
Username pgtype.Text
|
||||
Title pgtype.Text
|
||||
AccessHash pgtype.Int8
|
||||
Pts pgtype.Int4
|
||||
}
|
||||
|
||||
func (dto *getChannelsWithTrackedPostsDTO) destination() []any {
|
||||
return []any{
|
||||
&dto.ID,
|
||||
&dto.TelegramID,
|
||||
&dto.Username,
|
||||
&dto.Title,
|
||||
&dto.AccessHash,
|
||||
&dto.Pts,
|
||||
}
|
||||
}
|
||||
|
||||
func (dto *getChannelsWithTrackedPostsDTO) toDomain() domain.Channel {
|
||||
return domain.Channel{
|
||||
ID: dto.ID.Bytes,
|
||||
TelegramID: domain.NormalizeChatID(dto.TelegramID.Int64),
|
||||
Username: dto.Username.String,
|
||||
Title: dto.Title.String,
|
||||
AccessHash: dto.AccessHash.Int64,
|
||||
Pts: int(dto.Pts.Int32),
|
||||
}
|
||||
}
|
||||
90
tg_parser/internal/adapter/database/get_tracked_posts.go
Normal file
90
tg_parser/internal/adapter/database/get_tracked_posts.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"github.com/TelegramExchange/tgex-parser/internal/domain"
|
||||
"github.com/TelegramExchange/tgex-parser/pkg/transaction"
|
||||
)
|
||||
|
||||
func (d *Database) GetTrackedPosts(ctx context.Context, channel domain.Channel) ([]domain.Post, error) {
|
||||
query := `SELECT DISTINCT
|
||||
p.id,
|
||||
p.channel_id,
|
||||
p.message_id,
|
||||
p.text,
|
||||
CASE
|
||||
WHEN c.username IS NOT NULL THEN CONCAT('https://t.me/', c.username, '/', p.message_id)
|
||||
ELSE ''
|
||||
END as link,
|
||||
COALESCE(
|
||||
(SELECT pvh.views_count
|
||||
FROM post_views_history pvh
|
||||
WHERE pvh.post_id = p.id
|
||||
ORDER BY pvh.fetched_at DESC
|
||||
LIMIT 1),
|
||||
0
|
||||
) as views
|
||||
FROM post p
|
||||
INNER JOIN channel c ON c.id = p.channel_id
|
||||
INNER JOIN placement pl ON pl.post_id = p.id
|
||||
WHERE p.channel_id = $1
|
||||
AND p.deleted_from_channel_at IS NULL;`
|
||||
|
||||
txOrPool := transaction.TryExtractTX(ctx)
|
||||
|
||||
rows, err := txOrPool.Query(ctx, query, pgtype.UUID{Bytes: channel.ID, Valid: true})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("txOrPool.Query: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
posts := make([]domain.Post, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var dto getTrackedPostsDTO
|
||||
|
||||
err = rows.Scan(dto.destination()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rows.Scan: %w", err)
|
||||
}
|
||||
|
||||
posts = append(posts, dto.toDomain())
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
type getTrackedPostsDTO struct {
|
||||
ID pgtype.UUID
|
||||
ChannelID pgtype.UUID
|
||||
MessageID pgtype.Int4
|
||||
Text pgtype.Text
|
||||
Link pgtype.Text
|
||||
Views pgtype.Int4
|
||||
}
|
||||
|
||||
func (dto *getTrackedPostsDTO) destination() []any {
|
||||
return []any{
|
||||
&dto.ID,
|
||||
&dto.ChannelID,
|
||||
&dto.MessageID,
|
||||
&dto.Text,
|
||||
&dto.Link,
|
||||
&dto.Views,
|
||||
}
|
||||
}
|
||||
|
||||
func (dto *getTrackedPostsDTO) toDomain() domain.Post {
|
||||
return domain.Post{
|
||||
ID: dto.ID.Bytes,
|
||||
ChannelID: dto.ChannelID.Bytes,
|
||||
MessageID: int(dto.MessageID.Int32),
|
||||
Text: dto.Text.String,
|
||||
Link: dto.Link.String,
|
||||
Views: int(dto.Views.Int32),
|
||||
}
|
||||
}
|
||||
49
tg_parser/internal/adapter/database/update_channel.go
Normal file
49
tg_parser/internal/adapter/database/update_channel.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"github.com/TelegramExchange/tgex-parser/internal/domain"
|
||||
"github.com/TelegramExchange/tgex-parser/pkg/transaction"
|
||||
)
|
||||
|
||||
func (d *Database) UpdateChannel(ctx context.Context, channel domain.Channel) error {
|
||||
query := `UPDATE channel
|
||||
SET telegram_id = $2,
|
||||
username = $3,
|
||||
title = $4,
|
||||
access_hash = $5,
|
||||
pts = $6,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;`
|
||||
|
||||
txOrPool := transaction.TryExtractTX(ctx)
|
||||
|
||||
dto := updateChannelDTO{
|
||||
ID: pgtype.UUID{Bytes: channel.ID, Valid: true},
|
||||
TelegramID: pgtype.Int8{Int64: channel.TelegramID, Valid: true},
|
||||
Username: pgtype.Text{String: channel.Username, Valid: true},
|
||||
Title: pgtype.Text{String: channel.Title, Valid: true},
|
||||
AccessHash: pgtype.Int8{Int64: channel.AccessHash, Valid: true},
|
||||
Pts: pgtype.Int4{Int32: int32(channel.Pts), Valid: true},
|
||||
}
|
||||
|
||||
_, err := txOrPool.Exec(ctx, query, dto.ID, dto.TelegramID, dto.Username, dto.Title, dto.AccessHash, dto.Pts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("txOrPool.Exec: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type updateChannelDTO struct {
|
||||
ID pgtype.UUID
|
||||
TelegramID pgtype.Int8
|
||||
Username pgtype.Text
|
||||
Title pgtype.Text
|
||||
AccessHash pgtype.Int8
|
||||
Pts pgtype.Int4
|
||||
}
|
||||
Reference in New Issue
Block a user