opengist/internal/db/gist.go

415 lines
11 KiB
Go
Raw Normal View History

2023-09-02 22:30:57 +00:00
package db
2023-03-14 15:22:52 +00:00
import (
"github.com/labstack/echo/v4"
2023-05-15 19:07:29 +00:00
"github.com/thomiceli/opengist/internal/git"
"gorm.io/gorm"
"os/exec"
"strings"
2023-03-14 15:22:52 +00:00
"time"
)
type Gist struct {
ID uint `gorm:"primaryKey"`
Uuid string
2023-03-17 13:56:39 +00:00
Title string
2023-03-14 15:22:52 +00:00
Preview string
PreviewFilename string
2023-03-17 13:56:39 +00:00
Description string
Private int // 0: public, 1: unlisted, 2: private
2023-03-14 15:22:52 +00:00
UserID uint
2023-03-17 13:56:39 +00:00
User User
2023-03-14 15:22:52 +00:00
NbFiles int
NbLikes int
2023-03-14 22:26:39 +00:00
NbForks int
2023-03-14 15:22:52 +00:00
CreatedAt int64
UpdatedAt int64
2023-03-14 22:26:39 +00:00
Likes []User `gorm:"many2many:likes;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Forked *Gist `gorm:"foreignKey:ForkedID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
ForkedID uint
2023-03-14 15:22:52 +00:00
}
2023-06-21 16:19:17 +00:00
type Like struct {
UserID uint `gorm:"primaryKey"`
GistID uint `gorm:"primaryKey"`
CreatedAt int64
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) BeforeDelete(tx *gorm.DB) error {
// Decrement fork counter if the gist was forked
err := tx.Model(&Gist{}).
Omit("updated_at").
2023-03-17 13:56:39 +00:00
Where("id = ?", gist.ForkedID).
UpdateColumn("nb_forks", gorm.Expr("nb_forks - 1")).Error
return err
}
2023-03-14 15:22:52 +00:00
func GetGist(user string, gistUuid string) (*Gist, error) {
gist := new(Gist)
2023-03-14 22:26:39 +00:00
err := db.Preload("User").Preload("Forked.User").
2023-03-14 15:22:52 +00:00
Where("gists.uuid = ? AND users.username like ?", gistUuid, user).
Joins("join users on gists.user_id = users.id").
First(&gist).Error
return gist, err
}
func GetGistByID(gistId string) (*Gist, error) {
gist := new(Gist)
2023-03-14 22:26:39 +00:00
err := db.Preload("User").Preload("Forked.User").
2023-03-14 15:22:52 +00:00
Where("gists.id = ?", gistId).
First(&gist).Error
return gist, err
}
func GetAllGistsForCurrentUser(currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
var gists []*Gist
2023-03-14 22:26:39 +00:00
err := db.Preload("User").Preload("Forked.User").
2023-03-14 15:22:52 +00:00
Where("gists.private = 0 or gists.user_id = ?", currentUserId).
Limit(11).
Offset(offset * 10).
Order(sort + "_at " + order).
Find(&gists).Error
return gists, err
}
func GetAllGists(offset int) ([]*Gist, error) {
2023-03-17 13:56:39 +00:00
var gists []*Gist
2023-03-14 15:22:52 +00:00
err := db.Preload("User").
Limit(11).
Offset(offset * 10).
Order("id asc").
2023-03-17 13:56:39 +00:00
Find(&gists).Error
2023-03-14 15:22:52 +00:00
2023-03-17 13:56:39 +00:00
return gists, err
2023-03-14 15:22:52 +00:00
}
2023-06-21 16:19:17 +00:00
func GetAllGistsFromSearch(currentUserId uint, query string, offset int, sort string, order string) ([]*Gist, error) {
2023-03-14 15:22:52 +00:00
var gists []*Gist
2023-03-14 22:26:39 +00:00
err := db.Preload("User").Preload("Forked.User").
Where("((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
2023-06-21 16:19:17 +00:00
Where("gists.title like ? or gists.description like ?", "%"+query+"%", "%"+query+"%").
2023-03-14 15:22:52 +00:00
Limit(11).
Offset(offset * 10).
Order("gists." + sort + "_at " + order).
Find(&gists).Error
return gists, err
}
2023-06-21 16:19:17 +00:00
func gistsFromUserStatement(fromUserId uint, currentUserId uint) *gorm.DB {
return db.Preload("User").Preload("Forked.User").
Where("((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
2023-06-21 16:19:17 +00:00
Where("users.id = ?", fromUserId).
Joins("join users on gists.user_id = users.id")
}
func GetAllGistsFromUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
var gists []*Gist
err := gistsFromUserStatement(fromUserId, currentUserId).Limit(11).
Offset(offset * 10).
Order("gists." + sort + "_at " + order).
Find(&gists).Error
return gists, err
}
func CountAllGistsFromUser(fromUserId uint, currentUserId uint) (int64, error) {
var count int64
err := gistsFromUserStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error
return count, err
}
func likedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
return db.Preload("User").Preload("Forked.User").
Where("((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
2023-06-21 16:19:17 +00:00
Where("likes.user_id = ?", fromUserId).
Joins("join likes on gists.id = likes.gist_id").
Joins("join users on likes.user_id = users.id")
}
func GetAllGistsLikedByUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
var gists []*Gist
err := likedStatement(fromUserId, currentUserId).Limit(11).
Offset(offset * 10).
Order("gists." + sort + "_at " + order).
Find(&gists).Error
return gists, err
}
func CountAllGistsLikedByUser(fromUserId uint, currentUserId uint) (int64, error) {
var count int64
err := likedStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error
return count, err
}
func forkedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
return db.Preload("User").Preload("Forked.User").
Where("gists.forked_id is not null and ((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
2023-06-21 16:19:17 +00:00
Where("gists.user_id = ?", fromUserId).
Joins("join users on gists.user_id = users.id")
}
func GetAllGistsForkedByUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
var gists []*Gist
err := forkedStatement(fromUserId, currentUserId).Limit(11).
Offset(offset * 10).
Order("gists." + sort + "_at " + order).
Find(&gists).Error
return gists, err
}
func CountAllGistsForkedByUser(fromUserId uint, currentUserId uint) (int64, error) {
var count int64
err := forkedStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error
return count, err
}
func GetAllGistsRows() ([]*Gist, error) {
var gists []*Gist
err := db.Table("gists").
Preload("User").
Find(&gists).Error
return gists, err
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) Create() error {
2023-03-14 22:26:39 +00:00
// avoids foreign key constraint error because the default value in the struct is 0
return db.Omit("forked_id").Create(&gist).Error
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) CreateForked() error {
2023-03-14 15:22:52 +00:00
return db.Create(&gist).Error
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) Update() error {
2023-03-14 22:26:39 +00:00
return db.Omit("forked_id").Save(&gist).Error
2023-03-14 15:22:52 +00:00
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) Delete() error {
err := gist.DeleteRepository()
if err != nil {
return err
}
2023-03-14 15:22:52 +00:00
return db.Delete(&gist).Error
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) SetLastActiveNow() error {
2023-03-14 15:22:52 +00:00
return db.Model(&Gist{}).
2023-03-17 13:56:39 +00:00
Where("id = ?", gist.ID).
2023-03-14 15:22:52 +00:00
Update("updated_at", time.Now().Unix()).Error
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) AppendUserLike(user *User) error {
2023-03-14 22:26:39 +00:00
err := db.Model(&gist).Omit("updated_at").Update("nb_likes", gist.NbLikes+1).Error
if err != nil {
return err
}
2023-03-14 15:22:52 +00:00
return db.Model(&gist).Omit("updated_at").Association("Likes").Append(user)
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) RemoveUserLike(user *User) error {
2023-03-14 22:26:39 +00:00
err := db.Model(&gist).Omit("updated_at").Update("nb_likes", gist.NbLikes-1).Error
if err != nil {
return err
}
2023-03-14 15:22:52 +00:00
return db.Model(&gist).Omit("updated_at").Association("Likes").Delete(user)
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) IncrementForkCount() error {
2023-03-14 22:26:39 +00:00
return db.Model(&gist).Omit("updated_at").Update("nb_forks", gist.NbForks+1).Error
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) GetForkParent(user *User) (*Gist, error) {
2023-03-14 22:26:39 +00:00
fork := new(Gist)
err := db.Preload("User").
Where("forked_id = ? and user_id = ?", gist.ID, user.ID).
First(&fork).Error
return fork, err
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) GetUsersLikes(offset int) ([]*User, error) {
2023-03-14 15:22:52 +00:00
var users []*User
err := db.Model(&gist).
Where("gist_id = ?", gist.ID).
Limit(31).
Offset(offset * 30).
Association("Likes").Find(&users)
return users, err
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) GetForks(currentUserId uint, offset int) ([]*Gist, error) {
2023-03-14 22:26:39 +00:00
var gists []*Gist
err := db.Model(&gist).Preload("User").
Where("forked_id = ?", gist.ID).
Where("(gists.private = 0) or (gists.private > 0 and gists.user_id = ?)", currentUserId).
2023-03-14 22:26:39 +00:00
Limit(11).
Offset(offset * 10).
Order("updated_at desc").
Find(&gists).Error
return gists, err
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) CanWrite(user *User) bool {
2023-03-14 15:22:52 +00:00
return !(user == nil) && (gist.UserID == user.ID)
}
2023-03-17 13:56:39 +00:00
func (gist *Gist) InitRepository() error {
return git.InitRepository(gist.User.Username, gist.Uuid)
}
func (gist *Gist) InitRepositoryViaNewPush(ctx echo.Context) error {
return git.InitRepositoryViaNewPush(gist.User.Username, gist.Uuid, ctx)
}
func (gist *Gist) DeleteRepository() error {
return git.DeleteRepository(gist.User.Username, gist.Uuid)
}
2023-03-18 22:18:20 +00:00
func (gist *Gist) Files(revision string) ([]*git.File, error) {
var files []*git.File
filesStr, err := git.GetFilesOfRepository(gist.User.Username, gist.Uuid, revision)
if err != nil {
// if the revision or the file do not exist
if exiterr, ok := err.(*exec.ExitError); ok && exiterr.ExitCode() == 128 {
return nil, nil
}
return nil, err
}
for _, fileStr := range filesStr {
file, err := gist.File(revision, fileStr, true)
if err != nil {
return nil, err
}
files = append(files, file)
}
return files, err
}
2023-03-18 22:18:20 +00:00
func (gist *Gist) File(revision string, filename string, truncate bool) (*git.File, error) {
content, truncated, err := git.GetFileContent(gist.User.Username, gist.Uuid, revision, filename, truncate)
// if the revision or the file do not exist
if exiterr, ok := err.(*exec.ExitError); ok && exiterr.ExitCode() == 128 {
return nil, nil
}
2023-03-18 22:18:20 +00:00
return &git.File{
Filename: filename,
Content: content,
Truncated: truncated,
}, err
}
2023-04-16 14:14:12 +00:00
func (gist *Gist) Log(skip int) ([]*git.Commit, error) {
2023-03-18 22:18:20 +00:00
return git.GetLog(gist.User.Username, gist.Uuid, skip)
}
func (gist *Gist) NbCommits() (string, error) {
2023-09-16 22:59:47 +00:00
return git.CountCommits(gist.User.Username, gist.Uuid)
}
2023-03-18 22:23:23 +00:00
func (gist *Gist) AddAndCommitFiles(files *[]FileDTO) error {
2023-04-04 00:01:31 +00:00
if err := git.CloneTmp(gist.User.Username, gist.Uuid, gist.Uuid, gist.User.Email); err != nil {
return err
}
for _, file := range *files {
if err := git.SetFileContent(gist.Uuid, file.Filename, file.Content); err != nil {
return err
}
}
if err := git.AddAll(gist.Uuid); err != nil {
return err
}
2023-03-20 12:30:25 +00:00
if err := git.CommitRepository(gist.Uuid, gist.User.Username, gist.User.Email); err != nil {
return err
}
return git.Push(gist.Uuid)
}
func (gist *Gist) ForkClone(username string, uuid string) error {
return git.ForkClone(gist.User.Username, gist.Uuid, username, uuid)
}
func (gist *Gist) UpdateServerInfo() error {
return git.UpdateServerInfo(gist.User.Username, gist.Uuid)
}
func (gist *Gist) RPC(service string) ([]byte, error) {
return git.RPC(gist.User.Username, gist.Uuid, service)
}
func (gist *Gist) UpdatePreviewAndCount() error {
filesStr, err := git.GetFilesOfRepository(gist.User.Username, gist.Uuid, "HEAD")
if err != nil {
return err
}
gist.NbFiles = len(filesStr)
if len(filesStr) == 0 {
gist.Preview = ""
gist.PreviewFilename = ""
} else {
file, err := gist.File("HEAD", filesStr[0], true)
if err != nil {
return err
}
split := strings.Split(file.Content, "\n")
if len(split) > 10 {
gist.Preview = strings.Join(split[:10], "\n")
} else {
gist.Preview = file.Content
}
gist.PreviewFilename = file.Filename
}
return gist.Update()
}
2023-03-17 13:56:39 +00:00
// -- DTO -- //
type GistDTO struct {
2023-03-18 22:23:23 +00:00
Title string `validate:"max=50" form:"title"`
Description string `validate:"max=150" form:"description"`
Private int `validate:"number,min=0,max=2" form:"private"`
2023-03-18 22:23:23 +00:00
Files []FileDTO `validate:"min=1,dive"`
2023-09-16 22:59:47 +00:00
Name []string `form:"name"`
Content []string `form:"content"`
2023-03-18 22:23:23 +00:00
}
type FileDTO struct {
Filename string `validate:"excludes=\x2f,excludes=\x5c,max=50"`
Content string `validate:"required"`
2023-03-17 13:56:39 +00:00
}
func (dto *GistDTO) ToGist() *Gist {
return &Gist{
Title: dto.Title,
Description: dto.Description,
Private: dto.Private,
}
}
func (dto *GistDTO) ToExistingGist(gist *Gist) *Gist {
gist.Title = dto.Title
gist.Description = dto.Description
return gist
}