Files
panels/services/post-service/internal/post.go
2023-09-27 16:17:47 +01:00

162 lines
3.9 KiB
Go

package internal
import (
"regexp"
"context"
"strconv"
"encoding/json"
"database/sql"
"database/sql/driver"
"github.com/jackc/pgx/v5/pgtype"
"github.com/go-ozzo/ozzo-validation/v4"
)
// Post Models
type Post struct {
Id PostId `json:"id"`
PanelId string `json:"panel_id"`
AuthorId string `json:"author_id"`
Title string `json:"title"`
Content string `json:"content"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type PostCreate struct {
Title string `json:"title"`
Content string `json:"content"`
}
func (p *PostCreate) Validate() error {
return validation.ValidateStruct(
p,
validation.Field(&p.Title, validation.Required, validation.Length(3, 512), validation.Match(regexp.MustCompile("^[^_][\\w ]+[^_]$"))),
validation.Field(&p.Content, validation.Required, validation.Length(3, 2048)),
)
}
type PostUpdate struct {
Title *string `json:"title,omitempty"`
Content *string `json:"content,omitempty"`
}
func (p *PostUpdate) Validate() error {
return validation.ValidateStruct(
p,
validation.Field(&p.Title, validation.NilOrNotEmpty, validation.Length(3, 512), validation.Match(regexp.MustCompile("^[^_][\\w ]+[^_]$"))),
validation.Field(&p.Content, validation.NilOrNotEmpty, validation.Length(3, 2048)),
)
}
// Service Interface Methods
type PostService interface {
PostRepository
}
type PostRepository interface {
CreatePost(ctx context.Context, panelId string, authorId string, data PostCreate) (*Post, error)
GetPost(ctx context.Context, id PostId) (*Post, error)
GetPanelPost(ctx context.Context, id PostId, panelId string) (*Post, error)
UpdatePost(ctx context.Context, id PostId, data PostUpdate) (*Post, error)
DeletePost(ctx context.Context, id PostId) error
GetFeedPosts(ctx context.Context) ([]*Post, error)
GetUserPosts(ctx context.Context, userId string) ([]*Post, error)
GetPanelPosts(ctx context.Context, panelId string) ([]*Post, error)
}
type PostDBRepository interface {
PostRepository
DeletePostsByUser(ctx context.Context, userId string) ([]PostId, error)
DeletePostsOnPanel(ctx context.Context, panelId string) ([]PostId, error)
}
// Converts IDs between int64 (base 10) internally and string (base 36) externally
type PostId struct {
Id int64
}
func (pid *PostId) GetId() int64 {
if pid == nil {
return 0
}
return pid.Id
}
func (pid *PostId) GetReprId() string {
if pid.GetId() == 0 {
return ""
}
return strconv.FormatInt(pid.GetId(), 36)
}
func (pid *PostId) Scan(value interface{}) error {
scnr := sql.NullInt64{}
err := scnr.Scan(value)
if err != nil {
return NewServiceError(InvalidArgumentErrorCode, "failed to scan post id: must be of type int64")
} else if scnr.Int64 < 1 {
return NewServiceError(InvalidArgumentErrorCode, "invalid post id: value must be greater than 0")
}
pid.Id = scnr.Int64
return nil
}
func (pid PostId) Value() (driver.Value, error) {
if pid.GetId() == 0 {
return nil, NewServiceError(InvalidArgumentErrorCode, "post id not provided (of default value)")
}
return driver.Value(pid.GetId()), nil
}
func (pid PostId) MarshalJSON() ([]byte, error) {
return json.Marshal(pid.GetReprId())
}
func (pid *PostId) UnmarshalJSON(data []byte) error {
// Attempt to unmarshal the representative id
var repr_id string
err := json.Unmarshal(data, &repr_id)
if err != nil {
return err
}
// Reconstruct the ID using the representative ID
id, err := getIdFromRepr(repr_id)
if err != nil {
return err
}
pid.Id = *id
return nil
}
func NewPostId(id int64) (*PostId, error) {
if id < 1 {
return nil, NewServiceError(InvalidArgumentErrorCode, "invalid post id: value must be greater than 0")
}
return &PostId{Id: id}, nil
}
func NewPostIdFromRepr(reprId string) (*PostId, error) {
id, err := getIdFromRepr(reprId)
if err != nil {
return nil, err
}
return NewPostId(*id)
}
func getIdFromRepr(reprId string) (*int64, error) {
id, err := strconv.ParseInt(reprId, 36, 64)
return &id, err
}