Bug fixes (#184)

* Fix gist content when going back to editing

* Fix not outputting non-truncated large files for editon/zip download

* Allow dashes in usernames

* Delete keys associated to deleted user

* Fix error message when there is no files in gist

* Show if there is not files in gist preview

* Fix log parsing for the 11th empty commit
This commit is contained in:
Thomas Miceli 2023-12-27 12:11:02 +01:00
parent 3828022a1c
commit 3c97901995
13 changed files with 693 additions and 238 deletions

View file

@ -310,21 +310,21 @@ func (gist *Gist) DeleteRepository() error {
return git.DeleteRepository(gist.User.Username, gist.Uuid)
}
func (gist *Gist) Files(revision string) ([]*git.File, error) {
func (gist *Gist) Files(revision string, truncate bool) ([]*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, &git.RevisionNotFoundError{}
}
return nil, err
}
for _, fileStr := range filesStr {
file, err := gist.File(revision, fileStr, true)
file, err := gist.File(revision, fileStr, truncate)
if err != nil {
return nil, err
}

View file

@ -53,6 +53,11 @@ func (user *User) BeforeDelete(tx *gorm.DB) error {
return err
}
err = tx.Where("user_id = ?", user.ID).Delete(&SSHKey{}).Error
if err != nil {
return err
}
// Delete all gists created by this user
return tx.Where("user_id = ?", user.ID).Delete(&Gist{}).Error
}
@ -189,7 +194,7 @@ func (user *User) DeleteProviderID(provider string) error {
// -- DTO -- //
type UserDTO struct {
Username string `form:"username" validate:"required,max=24,alphanum,notreserved"`
Username string `form:"username" validate:"required,max=24,alphanumdash,notreserved"`
Password string `form:"password" validate:"required"`
}

View file

@ -22,6 +22,12 @@ var (
const truncateLimit = 2 << 18
type RevisionNotFoundError struct{}
func (m *RevisionNotFoundError) Error() string {
return "revision not found"
}
func RepositoryPath(user string, gist string) string {
return filepath.Join(config.GetHomeDir(), ReposDirectory, strings.ToLower(user), gist)
}

View file

@ -94,6 +94,11 @@ func parseLog(out io.Reader, maxBytes int) []*Commit {
scanner.Scan()
if len(scanner.Bytes()) == 0 {
commits = append(commits, currentCommit)
break
}
// if there is no shortstat, it means that the commit is empty, we add it and move onto the next one
if scanner.Bytes()[0] != ' ' {
commits = append(commits, currentCommit)

View file

@ -25,7 +25,7 @@ gist.raw: Raw
gist.file-truncated: This file has been truncated.
gist.watch-full-file: View the full file.
gist.file-not-valid: This file is not a valid CSV file.
gist.no-content: No content
gist.no-content: No files found
gist.new.new_gist: New gist
gist.new.title: Title

View file

@ -25,7 +25,7 @@ gist.raw: Brut
gist.file-truncated: Ce fichier a été tronqué.
gist.watch-full-file: Voir le fichier complet.
gist.file-not-valid: Ce fichier n'est pas un fichier CSV valide.
gist.no-content: Pas de contenu
gist.no-content: Aucun fichier
gist.new.new_gist: Nouveau gist
gist.new.title: Titre

View file

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"github.com/rs/zerolog/log"
"github.com/thomiceli/opengist/internal/git"
"github.com/thomiceli/opengist/internal/render"
"html/template"
"net/url"
@ -286,13 +287,11 @@ func gistIndex(ctx echo.Context) error {
revision = "HEAD"
}
files, err := gist.Files(revision)
if err != nil {
return errorRes(500, "Error fetching files", err)
}
if len(files) == 0 {
files, err := gist.Files(revision, true)
if _, ok := err.(*git.RevisionNotFoundError); ok {
return notFound("Revision not found")
} else if err != nil {
return errorRes(500, "Error fetching files", err)
}
renderedFiles, err := render.HighlightFiles(files)
@ -310,7 +309,7 @@ func gistIndex(ctx echo.Context) error {
func gistJson(ctx echo.Context) error {
gist := getData(ctx, "gist").(*db.Gist)
files, err := gist.Files("HEAD")
files, err := gist.Files("HEAD", true)
if err != nil {
return errorRes(500, "Error fetching files", err)
}
@ -358,7 +357,7 @@ func gistJs(ctx echo.Context) error {
}
gist := getData(ctx, "gist").(*db.Gist)
files, err := gist.Files("HEAD")
files, err := gist.Files("HEAD", true)
if err != nil {
return errorRes(500, "Error fetching files", err)
}
@ -481,7 +480,7 @@ func processCreate(ctx echo.Context) error {
if isCreate {
return html(ctx, "create.html")
} else {
files, err := gist.Files("HEAD")
files, err := gist.Files("HEAD", false)
if err != nil {
return errorRes(500, "Error fetching files", err)
}
@ -690,7 +689,7 @@ func downloadFile(ctx echo.Context) error {
func edit(ctx echo.Context) error {
gist := getData(ctx, "gist").(*db.Gist)
files, err := gist.Files("HEAD")
files, err := gist.Files("HEAD", false)
if err != nil {
return errorRes(500, "Error fetching files from repository", err)
}
@ -705,7 +704,7 @@ func downloadZip(ctx echo.Context) error {
gist := getData(ctx, "gist").(*db.Gist)
revision := ctx.Param("revision")
files, err := gist.Files(revision)
files, err := gist.Files(revision, true)
if err != nil {
return errorRes(500, "Error fetching files from repository", err)
}

776
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -34,41 +34,43 @@ document.querySelectorAll('.md-code-copy-btn').forEach(button => {
});
let checkboxes = document.querySelectorAll('li[data-checkbox-nb] input[type=checkbox]');
document.querySelectorAll<HTMLElement>('li[data-checkbox-nb]').forEach((el) => {
let input = el.firstElementChild;
(input as HTMLButtonElement).disabled = false;
let checkboxNb = (el as HTMLElement).dataset.checkboxNb;
let filename = input.parentElement.parentElement.parentElement.parentElement.parentElement.dataset.file;
if (document.getElementById('gist').dataset.own) {
document.querySelectorAll<HTMLElement>('li[data-checkbox-nb]').forEach((el) => {
let input: HTMLButtonElement = el.querySelector('input[type=checkbox]');
input.disabled = false;
let checkboxNb = (el as HTMLElement).dataset.checkboxNb;
let filename = input.closest<HTMLElement>('div[data-file]').dataset.file;
input.addEventListener('change', function () {
const data = new URLSearchParams();
data.append('checkbox', checkboxNb);
data.append('file', filename);
if (document.getElementsByName('_csrf').length !== 0) {
data.append('_csrf', ((document.getElementsByName('_csrf')[0] as HTMLInputElement).value));
}
checkboxes.forEach((el: HTMLButtonElement) => {
el.disabled = true;
el.classList.add('text-gray-400')
});
fetch(window.location.href.split('#')[0] + '/checkbox', {
method: 'PUT',
credentials: 'same-origin',
body: data,
}).then((response) => {
if (response.status === 200) {
checkboxes.forEach((el: HTMLButtonElement) => {
el.disabled = false;
el.classList.remove('text-gray-400')
});
input.addEventListener('change', function () {
const data = new URLSearchParams();
data.append('checkbox', checkboxNb);
data.append('file', filename);
if (document.getElementsByName('_csrf').length !== 0) {
data.append('_csrf', ((document.getElementsByName('_csrf')[0] as HTMLInputElement).value));
}
checkboxes.forEach((el: HTMLButtonElement) => {
el.disabled = true;
el.classList.add('text-gray-400')
});
fetch(window.location.href.split('#')[0] + '/checkbox', {
method: 'PUT',
credentials: 'same-origin',
body: data,
}).then((response) => {
if (response.status === 200) {
checkboxes.forEach((el: HTMLButtonElement) => {
el.disabled = false;
el.classList.remove('text-gray-400')
});
}
});
});
})
})
});
} else {
checkboxes.forEach((el: HTMLButtonElement) => {
el.disabled = true;
});
}

View file

@ -1,5 +1,5 @@
{{ define "gist_header" }}
<div class="py-10">
<div class="py-10" id="gist" data-own="{{ if .userLogged }}{{ if eq .gist.User.Username .userLogged.Username }}true{{ end }}{{ end }}">
<header>
<div class="flex flex-col lg:flex-row">
<div>

View file

@ -151,23 +151,27 @@
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}/{{ $gist.Identifier }}" class="text-slate-700 dark:text-slate-300">
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 overflow-auto hover:border-primary-600">
<div class="code overflow-auto">
{{ if isMarkdown $gist.PreviewFilename }}
<div class="chroma preview markdown markdown-body p-8">{{ $gist.HTML | safe }}</div>
{{ else }}
<table class="chroma table-code w-full whitespace-pre" data-filename="{{ $gist.PreviewFilename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
<tbody>
{{ $ii := "1" }}
{{ $i := toInt $ii }}
{{ range $line := $gist.Lines }}
{{ if $gist.PreviewFilename }}
{{ if isMarkdown $gist.PreviewFilename }}
<div class="chroma preview markdown markdown-body p-8">{{ $gist.HTML | safe }}</div>
{{ else }}
<table class="chroma table-code w-full whitespace-pre" data-filename="{{ $gist.PreviewFilename }}" style="font-size: 0.8em; border-spacing: 0; border-collapse: collapse;">
<tbody>
{{ $ii := "1" }}
{{ $i := toInt $ii }}
{{ range $line := $gist.Lines }}
<tr>
<td class="select-none line-num px-4">{{$i}}</td>
<td class="line-code">{{ $line | safe }}</td>
</tr>
{{ $i = inc $i }}
<tr>
<td class="select-none line-num px-4">{{$i}}</td>
<td class="line-code">{{ $line | safe }}</td>
</tr>
{{ $i = inc $i }}
{{ end }}
</tbody>
</table>
{{ end }}
</tbody>
</table>
{{ else }}
<div class="pl-4 py-0.5 text-xs"><p>{{ $.locale.Tr "gist.no-content" }}</p></div>
{{ end }}
</div>
</div>

View file

@ -55,7 +55,7 @@
</select>
</div>
</div>
<input type="hidden" value="" name="content" class="form-filecontent">
<input type="hidden" value="" name="content" class="form-filecontent" autocomplete="off">
</div>
</div>

View file

@ -90,7 +90,7 @@
</select>
</div>
</div>
<input type="hidden" value="{{ $file.Content }}" name="content" class="form-filecontent">
<input type="hidden" value="{{ $file.Content }}" name="content" class="form-filecontent" autocomplete="off">
</div>
{{ end }}
</div>