2023-03-14 15:22:52 +00:00
|
|
|
package models
|
|
|
|
|
|
|
|
import (
|
2023-03-14 23:52:24 +00:00
|
|
|
"gorm.io/gorm"
|
2023-03-18 15:18:24 +00:00
|
|
|
"opengist/internal/git"
|
|
|
|
"os/exec"
|
2023-03-18 23:27:30 +00:00
|
|
|
"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 bool
|
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-03-17 13:56:39 +00:00
|
|
|
func (gist *Gist) BeforeDelete(tx *gorm.DB) error {
|
2023-03-14 23:52:24 +00:00
|
|
|
// 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).
|
2023-03-14 23:52:24 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
func GetAllGistsFromUser(fromUser string, 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("users.username = ? and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", fromUser, currentUserId).
|
|
|
|
Joins("join users on gists.user_id = users.id").
|
|
|
|
Limit(11).
|
|
|
|
Offset(offset * 10).
|
|
|
|
Order("gists." + sort + "_at " + order).
|
|
|
|
Find(&gists).Error
|
|
|
|
|
|
|
|
return gists, err
|
|
|
|
}
|
|
|
|
|
2023-03-19 00:51:25 +00:00
|
|
|
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 {
|
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 = 1 and gists.user_id = ?)", currentUserId).
|
|
|
|
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
|
|
|
|
2023-03-18 15:18:24 +00:00
|
|
|
func (gist *Gist) InitRepository() error {
|
|
|
|
return git.InitRepository(gist.User.Username, gist.Uuid)
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2023-03-18 15:18:24 +00:00
|
|
|
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) {
|
2023-03-18 15:18:24 +00:00
|
|
|
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{
|
2023-03-18 15:18:24 +00:00
|
|
|
Filename: filename,
|
|
|
|
Content: content,
|
|
|
|
Truncated: truncated,
|
|
|
|
}, err
|
|
|
|
}
|
|
|
|
|
2023-03-18 22:18:20 +00:00
|
|
|
func (gist *Gist) Log(skip string) ([]*git.Commit, error) {
|
|
|
|
return git.GetLog(gist.User.Username, gist.Uuid, skip)
|
2023-03-18 15:18:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (gist *Gist) NbCommits() (string, error) {
|
|
|
|
return git.GetNumberOfCommitsOfRepository(gist.User.Username, gist.Uuid)
|
|
|
|
}
|
|
|
|
|
2023-03-18 22:23:23 +00:00
|
|
|
func (gist *Gist) AddAndCommitFiles(files *[]FileDTO) error {
|
2023-03-18 15:18:24 +00:00
|
|
|
if err := git.CloneTmp(gist.User.Username, gist.Uuid, gist.Uuid); 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-18 22:18:20 +00:00
|
|
|
if err := git.CommitRepository(gist.Uuid); err != nil {
|
2023-03-18 15:18:24 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2023-03-18 23:27:30 +00:00
|
|
|
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.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 bool `form:"private"`
|
|
|
|
Files []FileDTO `validate:"min=1,dive"`
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
gist.Private = dto.Private
|
|
|
|
return gist
|
|
|
|
}
|