Compare commits

...

15 Commits

Author SHA1 Message Date
Thomas Miceli
3444fb9b75 v1.5.3 2023-11-20 18:49:46 +01:00
Thomas Miceli
be46304e23 Display OAuth errors (#159) 2023-11-20 18:41:01 +01:00
Thomas Miceli
5fa55dfbba Tiny UI fixes (#158) 2023-11-20 18:28:13 +01:00
Thomas Miceli
09fb647f03 Fix: bare first branch name, truncated output hanging (#157) 2023-11-20 18:03:59 +01:00
Thomas Miceli
d518a44d32 Create/change account password (#156) 2023-11-20 18:03:28 +01:00
Thomas Miceli
dcacde0959 Fix home user directory detection handling (#145) 2023-10-31 15:23:15 +09:00
Manuel Vergara
064d4d53f6 Add spanish translation (#139) 2023-10-31 15:22:58 +09:00
Thomas Miceli
aec7ee2708 v1.5.2 2023-10-16 12:26:05 +02:00
Thomas Miceli
10fd170833 Fix markdown render dark background (#137) 2023-10-16 12:20:09 +02:00
Slava Krampetz
ba03b8df38 Add ru-RU translation (#135) 2023-10-15 18:09:54 +02:00
Thomas Miceli
ef45f3d0ca config.yml with Docker (#131) 2023-10-15 08:14:34 +02:00
Thomas Miceli
b1acea9f1c Better password hashes error handling (#132) 2023-10-13 05:36:00 +02:00
Gary Wang
7059d5c834 Add zh-CN translation and minor UI fix (#130) 2023-10-12 14:13:39 +02:00
Thomas Miceli
1539499294 Longer title and description (#129) 2023-10-04 18:48:02 +02:00
Thomas Miceli
6f587f4757 Fix private gist visibility (#128) 2023-10-04 18:47:50 +02:00
24 changed files with 713 additions and 36 deletions

View File

@@ -1,5 +1,29 @@
# Changelog
## [1.5.3](https://github.com/thomiceli/opengist/compare/v1.5.2...v1.5.3) - 2023-11-20
### Added
- es-ES translation (#139)
- Create/change account password (#156)
- Display OAuth error messages when HTTP 400 (#159)
### Fixed
- Git bare repository branch name creation (#157)
- Git file truncated output hanging (#157)
- Home user directory detection handling (#145)
- UI changes (#158)
## [1.5.2](https://github.com/thomiceli/opengist/compare/v1.5.1...v1.5.2) - 2023-10-16
### Added
- zh-CN translation (#130)
- ru-RU translation (#135)
- config.yml usage in the Docker container (#131)
- Longer title and description (#129)
### Fixed
- Private gist visibility (#128)
- Dark background color in Markdown rendering (#137)
- Error handling for password hashes (#132)
## [1.5.1](https://github.com/thomiceli/opengist/compare/v1.5.0...v1.5.1) - 2023-09-29
### Added
- Hungarian translations (#123)

View File

@@ -40,6 +40,8 @@ RUN apk update && \
RUN addgroup -S opengist && \
adduser -S -G opengist -H -s /bin/ash -g 'Opengist User' opengist
COPY --from=build --chown=opengist:opengist /opengist/config.yml config.yml
WORKDIR /app/opengist
COPY --from=build --chown=opengist:opengist /opengist/opengist .

View File

@@ -77,14 +77,16 @@ Download the archive for your system from the release page [here](https://github
```shell
# example for linux amd64
wget https://github.com/thomiceli/opengist/releases/download/v1.5.1/opengist1.5.1-linux-amd64.tar.gz
wget https://github.com/thomiceli/opengist/releases/download/v1.5.2/opengist1.5.2-linux-amd64.tar.gz
tar xzvf opengist1.5.1-linux-amd64.tar.gz
tar xzvf opengist1.5.2-linux-amd64.tar.gz
cd opengist
chmod +x opengist
./opengist # with or without `--config config.yml`
```
Opengist is now running on port 6157, you can browse http://localhost:6157
### From source
Requirements : [Git](https://git-scm.com/downloads) (2.20+), [Go](https://go.dev/doc/install) (1.20+), [Node.js](https://nodejs.org/en/download/) (16+)

View File

@@ -7,5 +7,6 @@ groupmod -o -g "$GID" $USER
usermod -o -u "$UID" $USER
chown -R "$USER:$USER" /opengist
chown -R "$USER:$USER" /config.yml
exec su $USER -c "OG_OPENGIST_HOME=/opengist /app/opengist/opengist"
exec su $USER -c "OG_OPENGIST_HOME=/opengist /app/opengist/opengist --config /config.yml"

View File

@@ -12,6 +12,17 @@ The [configuration cheat sheet](cheat-sheet.md) lists all available configuratio
The configuration file must be specified when launching the application, using the `--config` flag followed by the path
to your YAML file.
Usage with Docker Compose :
```yml
services:
opengist:
# ...
volumes:
# ...
- "/path/to/config.yml:/config.yml"
```
Usage via command line :
```shell
./opengist --config /path/to/config.yml
```
@@ -22,7 +33,6 @@ You can start by copying and/or modifying the provided [config.yml](/config.yml)
## Configuration via Environment Variables
Usage with Docker Compose :
```yml
services:
opengist:
@@ -31,8 +41,8 @@ services:
OG_LOG_LEVEL: "info"
# etc.
```
Usage via command line :
Usage via command line :
```shell
OG_LOG_LEVEL=info ./opengist
```

View File

@@ -15,7 +15,7 @@ import (
"gopkg.in/yaml.v3"
)
var OpengistVersion = "1.5.1"
var OpengistVersion = "1.5.3"
var C *config
@@ -52,14 +52,10 @@ type config struct {
}
func configWithDefaults() (*config, error) {
homeDir, err := os.UserHomeDir()
c := &config{}
if err != nil {
return c, err
}
c.LogLevel = "warn"
c.OpengistHome = filepath.Join(homeDir, ".opengist")
c.OpengistHome = ""
c.DBFilename = "opengist.db"
c.SqliteJournalMode = "WAL"
@@ -93,6 +89,15 @@ func InitConfig(configPath string) error {
return err
}
if c.OpengistHome == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("opengist home directory is not set and current user home directory could not be determined; please specify the opengist home directory manually via the configuration")
}
c.OpengistHome = filepath.Join(homeDir, ".opengist")
}
if err = checks(c); err != nil {
return err
}

View File

@@ -386,8 +386,8 @@ func (gist *Gist) UpdatePreviewAndCount() error {
// -- DTO -- //
type GistDTO struct {
Title string `validate:"max=50" form:"title"`
Description string `validate:"max=150" form:"description"`
Title string `validate:"max=250" form:"title"`
Description string `validate:"max=1000" form:"description"`
Private int `validate:"number,min=0,max=2" form:"private"`
Files []FileDTO `validate:"min=1,dive"`
Name []string `form:"name"`
@@ -395,7 +395,7 @@ type GistDTO struct {
}
type FileDTO struct {
Filename string `validate:"excludes=\x2f,excludes=\x5c,max=50"`
Filename string `validate:"excludes=\x2f,excludes=\x5c,max=255"`
Content string `validate:"required"`
}

View File

@@ -2,6 +2,7 @@ package git
import (
"bytes"
"context"
"fmt"
"github.com/labstack/echo/v4"
"github.com/rs/zerolog/log"
@@ -12,6 +13,7 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
)
var (
@@ -121,7 +123,12 @@ func GetFileContent(user string, gist string, revision string, filename string,
maxBytes = truncateLimit
}
cmd := exec.Command(
// Set up a context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
cmd := exec.CommandContext(
ctx,
"git",
"--no-pager",
"show",
@@ -129,22 +136,17 @@ func GetFileContent(user string, gist string, revision string, filename string,
)
cmd.Dir = repositoryPath
stdout, _ := cmd.StdoutPipe()
err := cmd.Start()
output, err := cmd.Output()
if err != nil {
return "", false, err
}
output, truncated, err := truncateCommandOutput(stdout, maxBytes)
content, truncated, err := truncateCommandOutput(bytes.NewReader(output), maxBytes)
if err != nil {
return "", false, err
}
if err := cmd.Wait(); err != nil {
return "", false, err
}
return output, truncated, nil
return content, truncated, nil
}
func GetLog(user string, gist string, skip int) ([]*Commit, error) {
@@ -459,6 +461,12 @@ fi
const postReceive = `#!/bin/sh
while read oldrev newrev refname; do
if ! git rev-parse --verify --quiet HEAD &>/dev/null; then
git symbolic-ref HEAD "$refname"
fi
done
echo ""
echo "Your new repository has been created here: %s"
echo ""

View File

@@ -53,6 +53,8 @@ func (store *LocaleStore) loadLocaleFromYAML(localeCode, path string) error {
name := display.Self.Name(tag)
if tag == language.AmericanEnglish {
name = "English"
} else if tag == language.EuropeanSpanish {
name = "Español"
}
locale := &Locale{

View File

@@ -80,7 +80,7 @@ gist.revision.go-to-revision: Go to revision
gist.revision.file-created: file created
gist.revision.file-deleted: file deleted
gist.revision.file-renamed: renamed to
gist.revision.diff-truncated: Diff truncated because it's too large to be shown
gist.revision.diff-truncated: Diff is too large to be shown
gist.revision.file-renamed-no-changes: File renamed without changes
gist.revision.empty-file: Empty file
gist.revision.no-changes: No changes
@@ -106,6 +106,11 @@ settings.delete-ssh-key-confirm: Confirm deletion of SSH key
settings.ssh-key-added-at: Added
settings.ssh-key-never-used: Never used
settings.ssh-key-last-used: Last used
settings.create-password: Create password
settings.create-password-help: Create your password to login to Opengist via HTTP
settings.change-password: Change password
settings.change-password-help: Change your password to login to Opengist via HTTP
settings.password-label-title: Password
auth.signup-disabled: Administrator has disabled signing up
auth.login: Login

View File

@@ -0,0 +1,177 @@
gist.public: Público
gist.unlisted: No listado
gist.private: Privado
gist.header.like: Me gusta
gist.header.unlike: No me gusta
gist.header.fork: Bifurcar
gist.header.edit: Editar
gist.header.delete: Eliminar
gist.header.forked-from: Bifurcado desde
gist.header.last-active: Última actividad
gist.header.select-tab: Seleccionar pestaña
gist.header.code: Código
gist.header.revisions: Revisiones
gist.header.revision: Revisión
gist.header.clone-http: Clonar via %s
gist.header.clone-http-help: Clonar con Git usando autenticación básica HTTP.
gist.header.clone-ssh: Clonar via SSH
gist.header.clone-ssh-help: Clonar con Git usando una clave SSH.
gist.header.share: Compartir
gist.header.share-help: Copiar enlace para compartir este gist.
gist.header.download-zip: Descargar ZIP
gist.raw: Sin formato
gist.file-truncated: Este archivo ha sido truncado.
gist.watch-full-file: Ver el archivo completo.
gist.file-not-valid: Este archivo no es un archivo CSV válido.
gist.no-content: Sin contenido
gist.new.new_gist: Nuevo gist
gist.new.title: Título
gist.new.description: Descripción
gist.new.filename-with-extension: Nombre de archivo con extensión
gist.new.indent-mode: Modo de sangrado
gist.new.indent-mode-space: Espacio
gist.new.indent-mode-tab: Tabulación
gist.new.indent-size: Tamaño de sangrado
gist.new.wrap-mode: Modo de ajuste
gist.new.wrap-mode-no: Sin ajuste
gist.new.wrap-mode-soft: Ajuste suave
gist.new.add-file: Agregar archivo
gist.new.create-public-button: Crear gist público
gist.new.create-unlisted-button: Crear gist no listado
gist.new.create-private-button: Crear gist privado
gist.edit.editing: Editando
gist.edit.change-visibility: Hacer
gist.edit.delete: Eliminar
gist.edit.cancel: Cancelar
gist.edit.save: Guardar
gist.list.joined: Unido
gist.list.all: Todos los gists
gist.list.search-results: Resultados de búsqueda
gist.list.sort: Ordenar
gist.list.sort-by-created: creado
gist.list.sort-by-updated: actualizado
gist.list.order-by-asc: Menos reciente
gist.list.order-by-desc: Recientemente
gist.list.select-tab: Seleccionar pestaña
gist.list.liked: Gustado
gist.list.likes: gustos
gist.list.forked: Bifurcado
gist.list.forked-from: Bifurcado desde
gist.list.forks: bifurcaciones
gist.list.files: archivos
gist.list.last-active: Última actividad
gist.list.no-gists: Sin gists
gist.forks: Bifurcaciones
gist.forks.view: Ver bifurcación
gist.forks.no: No hay bifurcaciones públicas
gist.likes: Gustos
gist.likes.no: Aún no hay gustos
gist.revisions: Revisiones
gist.revision.revised: revisó este gist
gist.revision.go-to-revision: Ir a la revisión
gist.revision.file-created: archivo creado
gist.revision.file-deleted: archivo eliminado
gist.revision.file-renamed: renombrado a
gist.revision.diff-truncated: Diferencia truncada porque es demasiado grande para mostrarse.
gist.revision.file-renamed-no-changes: Archivo renombrado sin cambios
gist.revision.empty-file: Archivo vacío
gist.revision.no-changes: Sin cambios
gist.revision.no-revisions: No hay revisiones para mostrar
settings: Configuración
settings.email: Correo electrónico
settings.email-help: Usado para confirmaciones y Gravatar
settings.email-set: Establecer correo electrónico
settings.link-accounts: Enlazar cuentas
settings.link-github-account: Enlazar cuenta de GitHub
settings.link-gitea-account: Enlazar cuenta de Gitea
settings.unlink-github-account: Desenlazar cuenta de GitHub
settings.unlink-gitea-account: Desenlazar cuenta de Gitea
settings.delete-account: Eliminar cuenta
settings.delete-account-confirm: ¿Estás seguro de que quieres eliminar tu cuenta?
settings.add-ssh-key: Agregar clave SSH
settings.add-ssh-key-help: Usado solo para extraer/push gists usando Git a través de SSH
settings.add-ssh-key-title: Título
settings.add-ssh-key-content: Clave
settings.delete-ssh-key: Eliminar
settings.delete-ssh-key-confirm: Confirmar eliminación de clave SSH
settings.ssh-key-added-at: Añadido
settings.ssh-key-never-used: Nunca usado
settings.ssh-key-last-used: Último uso
auth.signup-disabled: El administrador ha deshabilitado el registro
auth.login: Iniciar sesión
auth.signup: Registrarse
auth.new-account: Nueva cuenta
auth.username: Nombre de usuario
auth.password: Contraseña
auth.register-instead: Registrarse en su lugar
auth.login-instead: Iniciar sesión en su lugar
auth.github-oauth: Continuar con cuenta de GitHub
auth.gitea-oauth: Continuar con cuenta de Gitea
error: Error
header.menu.all: Todos
header.menu.new: Nuevo
header.menu.search: Buscar
header.menu.my-gists: Mis gists
header.menu.liked: Gustados
header.menu.admin: Administrador
header.menu.settings: Configuración
header.menu.logout: Cerrar sesión
header.menu.register: Registrarse
header.menu.login: Iniciar sesión
header.menu.light: Claro
header.menu.dark: Oscuro
header.menu.system: Sistema
footer.powered-by: Desarrollado por %s
pagination.older: Anterior
pagination.newer: Siguiente
pagination.previous: Anterior
pagination.next: Siguiente
admin.admin_panel: Panel de administración
admin.general: General
admin.users: Usuarios
admin.gists: Gists
admin.configuration: Configuración
admin.versions: Versiones
admin.ssh_keys: Claves SSH
admin.stats: Estadísticas
admin.actions: Acciones
admin.actions.sync-fs: Sincronizar gists desde el sistema de archivos
admin.actions.sync-db: Sincronizar gists desde la base de datos
admin.actions.git-gc: Recolectar basura en los repositorios Git
admin.id: ID
admin.user: Usuario
admin.delete: Eliminar
admin.created_at: Creado
admin.config-link: Esta configuración puede ser %s por un archivo de configuración YAML y/o variables de entorno.
admin.config-link-overridden: sobrescrito
admin.disable-signup: Deshabilitar registro
admin.disable-signup_help: Prohibir la creación de nuevas cuentas.
admin.require-login: Requerir inicio de sesión
admin.require-login_help: Obligar a los usuarios a iniciar sesión para ver gists.
admin.disable-login: Deshabilitar formulario de inicio de sesión
admin.disable-login_help: Prohibir el inicio de sesión a través del formulario de inicio de sesión para forzar el uso de proveedores de OAuth en su lugar.
admin.disable-gravatar: Deshabilitar Gravatar
admin.disable-gravatar_help: Deshabilitar el uso de Gravatar como proveedor de avatar.
admin.users.delete_confirm: ¿Quieres eliminar a este usuario?
admin.gists.title: Título
admin.gists.private: ¿Privado?
admin.gists.nb-files: Núm. de archivos
admin.gists.nb-likes: Núm. de gustos
admin.gists.delete_confirm: ¿Quieres eliminar este gist?

View File

@@ -80,7 +80,7 @@ gist.revision.go-to-revision: Aller à la révision
gist.revision.file-created: fichier créé
gist.revision.file-deleted: fichier supprimé
gist.revision.file-renamed: renommé en
gist.revision.diff-truncated: Révision tronquée car trop volumineuse pour être affichée
gist.revision.diff-truncated: Révision trop volumineuse pour être affichée
gist.revision.file-renamed-no-changes: Fichier renommé sans modifications
gist.revision.empty-file: Fichier vide
gist.revision.no-changes: Aucun changement

View File

@@ -0,0 +1,177 @@
gist.public: Публичный
gist.unlisted: Скрытый
gist.private: Приватный
gist.header.like: Нравится
gist.header.unlike: Не нравится
gist.header.fork: Создать форк
gist.header.edit: Редактировать
gist.header.delete: Удалить
gist.header.forked-from: Форк с
gist.header.last-active: Последняя активность
gist.header.select-tab: Перейти
gist.header.code: Код
gist.header.revisions: Версии
gist.header.revision: Версия
gist.header.clone-http: Клонировать с помощью %s
gist.header.clone-http-help: Клонировать с помощью Git используя аутентификацию HTTP.
gist.header.clone-ssh: Клонировать c помощью SSH
gist.header.clone-ssh-help: Клонировать c помощью Git используя ключ SSH.
gist.header.share: Поделиться
gist.header.share-help: Скопировать ссылку на фрагмент.
gist.header.download-zip: Скачать ZIP-архив
gist.raw: Исходник
gist.file-truncated: Файл был обрезан.
gist.watch-full-file: Просмотр всего файла.
gist.file-not-valid: Невалидный CSV.
gist.no-content: Нет данных
gist.new.new_gist: Новый фрагмент
gist.new.title: Название
gist.new.description: Описание
gist.new.filename-with-extension: Имя файла с расширением
gist.new.indent-mode: Отступы
gist.new.indent-mode-space: Пробелы
gist.new.indent-mode-tab: Табуляция
gist.new.indent-size: Размер отступа
gist.new.wrap-mode: Переносы строк
gist.new.wrap-mode-no: Без переносов
gist.new.wrap-mode-soft: Мягкие переносы
gist.new.add-file: Добавить файл
gist.new.create-public-button: Создать публичный фрагмент
gist.new.create-unlisted-button: Создать скрытый фрагмент
gist.new.create-private-button: Создать приватный фрагмент
gist.edit.editing: Редактирование
gist.edit.change-visibility: Применить
gist.edit.delete: Удалить
gist.edit.cancel: Отмена
gist.edit.save: Сохранить
gist.list.joined: Зарегистрирован
gist.list.all: Все фрагменты
gist.list.search-results: Результаты поиска
gist.list.sort: Сортировка
gist.list.sort-by-created: по дате создания
gist.list.sort-by-updated: по дате обновления
gist.list.order-by-asc: Свежие снизу
gist.list.order-by-desc: Свежие сверху
gist.list.select-tab: Перейти
gist.list.liked: Понравившиеся
gist.list.likes: лайк(-ов)
gist.list.forked: Форки
gist.list.forked-from: Форки с
gist.list.forks: форк(-ов)
gist.list.files: файл(-ов)
gist.list.last-active: Последняя активность
gist.list.no-gists: Нет фрагментов
gist.forks: Форки
gist.forks.view: Посмотреть форк
gist.forks.no: Нет форков
gist.likes: Нравятся
gist.likes.no: Нет
gist.revisions: Ревизии
gist.revision.revised: ревизий этого фрагмента
gist.revision.go-to-revision: К ревизии
gist.revision.file-created: файл создан
gist.revision.file-deleted: файл удалён
gist.revision.file-renamed: переименован в
gist.revision.diff-truncated: Разница (diff) обрезана, так как результат слишком большой для показа
gist.revision.file-renamed-no-changes: Файл переименован без изменений
gist.revision.empty-file: Пустой файл
gist.revision.no-changes: Без изменений
gist.revision.no-revisions: Нет ревизий
settings: Настройки
settings.email: Адрес эл. почты
settings.email-help: Нужен для коммитов и Gravatar
settings.email-set: Сохранить адрес
settings.link-accounts: Привязка доступов
settings.link-github-account: Привязать доступ GitHub
settings.link-gitea-account: Привязать доступ Gitea
settings.unlink-github-account: Отвязать доступ GitHub
settings.unlink-gitea-account: Отвязать доступ Gitea
settings.delete-account: Удалить аккаунт
settings.delete-account-confirm: Вы уверены что хотите удалить свой аккаунт?
settings.add-ssh-key: Добавить ключ SSH
settings.add-ssh-key-help: Нужен только для работы с фрагментами через Git+SSH
settings.add-ssh-key-title: Название
settings.add-ssh-key-content: Ключ
settings.delete-ssh-key: Удалить
settings.delete-ssh-key-confirm: Подтвердите удаления ключа SSH
settings.ssh-key-added-at: Дата добавления
settings.ssh-key-never-used: Не был использован
settings.ssh-key-last-used: Последнее использование
auth.signup-disabled: Регистрация запрещена Администратором сервиса
auth.login: Вход
auth.signup: Регистрация
auth.new-account: Новый аккаунт
auth.username: Имя пользователя
auth.password: Пароль
auth.register-instead: Зарегистрироваться
auth.login-instead: Войти
auth.github-oauth: Войти с помощью доступа GitHub
auth.gitea-oauth: Войти с помощью доступа Gitea
error: Ошибка
header.menu.all: Все
header.menu.new: Новый
header.menu.search: Поиск
header.menu.my-gists: Мои фрагменты
header.menu.liked: Понравившиеся
header.menu.admin: Администрирование
header.menu.settings: Настройки
header.menu.logout: Выйти
header.menu.register: Регистрация
header.menu.login: Войти
header.menu.light: Светлая
header.menu.dark: Тёмная
header.menu.system: Системная
footer.powered-by: Работает на %s
pagination.older: Позже
pagination.newer: Новее
pagination.previous: Предыдущий
pagination.next: Следующий
admin.admin_panel: Панель управления
admin.general: Общее
admin.users: Пользователи
admin.gists: Фрагменты
admin.configuration: Настройки
admin.versions: Версии
admin.ssh_keys: Ключи SSH
admin.stats: Статистика
admin.actions: Действия
admin.actions.sync-fs: Синхронизировать фрагменты из файловой системы
admin.actions.sync-db: Синхронизировать фрагменты с базой данных
admin.actions.git-gc: Сборка мусора в репозиториях Git
admin.id: ID
admin.user: Пользователь
admin.delete: Удалить
admin.created_at: Создан
admin.config-link: Эти настройки могут быть %s файлом конфигурации YAML и/или переменными окружения.
admin.config-link-overriden: перекрыты
admin.disable-signup: Запретить регистрацию
admin.disable-signup_help: Запретить создание новых доступов
admin.require-login: Требовать авторизацию
admin.require-login_help: Запретить просмотр фрагментов без авторизации.
admin.disable-login: Запретить авторизацию по паролю
admin.disable-login_help: Запретить авторизацию с вводом пароля, форсировать внешнюю авторизацию через Gitea/GitHub.
admin.disable-gravatar: Запретить Gravatar
admin.disable-gravatar_help: Запретить использование Gravatar как провайдера изображений профиля.
admin.users.delete_confirm: Вы уверены что хотите удалить этого пользователя?
admin.gists.title: Название
admin.gists.private: Приватный
admin.gists.nb-files: Файлов
admin.gists.nb-likes: Понравилось
admin.gists.delete_confirm: Вы уверены что хотите удалить этот фрагмент?

View File

@@ -0,0 +1,177 @@
gist.public: 公开
gist.unlisted: 非列出
gist.private: 私有
gist.header.like: 喜欢
gist.header.unlike: 取消喜欢
gist.header.fork: 派生
gist.header.edit: 编辑
gist.header.delete: 删除
gist.header.forked-from: 派生自
gist.header.last-active: 最后活跃于
gist.header.select-tab: Select a tab
gist.header.code: 代码
gist.header.revisions: 修订
gist.header.revision: 修订
gist.header.clone-http: 通过 %s 克隆
gist.header.clone-http-help: 使用 Git 通过 HTTP 基础认证克隆。
gist.header.clone-ssh: 通过 SSH 克隆
gist.header.clone-ssh-help: 使用 Git 通过 SSH 密钥克隆。
gist.header.share: 分享
gist.header.share-help: 为此 Gist 复制可供分享的链接。
gist.header.download-zip: 下载 ZIP
gist.raw: 原始文件
gist.file-truncated: 此文件已被截断。
gist.watch-full-file: 查看完整文件。
gist.file-not-valid: 此文件不是有效的 CSV 文件。
gist.no-content: 没有内容
gist.new.new_gist: 创建 Gist
gist.new.title: 标题
gist.new.description: 描述
gist.new.filename-with-extension: 文件名与扩展名
gist.new.indent-mode: 缩进模式
gist.new.indent-mode-space: 空格
gist.new.indent-mode-tab: 制表符
gist.new.indent-size: 缩进大小
gist.new.wrap-mode: 换行模式
gist.new.wrap-mode-no: 不自动换行
gist.new.wrap-mode-soft: 软换行
gist.new.add-file: 添加文件
gist.new.create-public-button: 创建公开 Gist
gist.new.create-unlisted-button: 创建非列出 Gist
gist.new.create-private-button: 创建私有 Gist
gist.edit.editing: 编辑
gist.edit.change-visibility: 设为
gist.edit.delete: 删除
gist.edit.cancel: 取消
gist.edit.save: 保存
gist.list.joined: Joined
gist.list.all: 所有 Gists
gist.list.search-results: 搜索结果
gist.list.sort: 排序
gist.list.sort-by-created: 创建
gist.list.sort-by-updated: 更新
gist.list.order-by-asc: 最早
gist.list.order-by-desc: 最近
gist.list.select-tab: Select a tab
gist.list.liked: 已喜欢
gist.list.likes: 喜欢
gist.list.forked: 已派生
gist.list.forked-from: 派生自
gist.list.forks: 派生
gist.list.files: 文件
gist.list.last-active: 最后活跃于
gist.list.no-gists: 没有 Gist
gist.forks: 派生
gist.forks.view: 查看派生
gist.forks.no: 无公开派生
gist.likes: 喜欢
gist.likes.no: 还没有喜欢
gist.revisions: 修订
gist.revision.revised: 修订了这个 Gist
gist.revision.go-to-revision: 跳至此修订
gist.revision.file-created: file created
gist.revision.file-deleted: file deleted
gist.revision.file-renamed: 重命名为
gist.revision.diff-truncated: 由于变更差异过大,显示内容已被截断
gist.revision.file-renamed-no-changes: File renamed without changes
gist.revision.empty-file: 空文件
gist.revision.no-changes: 没有变更
gist.revision.no-revisions: 无可供显示的修订
settings: 设置
settings.email: 邮箱
settings.email-help: 用于提交与 Gravatar
settings.email-set: 设置邮箱地址
settings.link-accounts: 关联账号
settings.link-github-account: 关联 GitHub 账号
settings.link-gitea-account: 关联 Gitea 账号
settings.unlink-github-account: 解除关联 GitHub 账号
settings.unlink-gitea-account: 解除关联 Gitea 账号
settings.delete-account: 删除账号
settings.delete-account-confirm: 您确认要删除您的账号吗?
settings.add-ssh-key: 添加 SSH 密钥
settings.add-ssh-key-help: 用于使用 Git 通过 SSH 拉取与推送 Gist
settings.add-ssh-key-title: 标题
settings.add-ssh-key-content: 密钥
settings.delete-ssh-key: 删除
settings.delete-ssh-key-confirm: Confirm deletion of SSH key
settings.ssh-key-added-at: 添加
settings.ssh-key-never-used: 从未使用过
settings.ssh-key-last-used: 最后使用于
auth.signup-disabled: 管理员已禁用了注册
auth.login: 登录
auth.signup: 注册
auth.new-account: 新建账号
auth.username: 用户名
auth.password: 密码
auth.register-instead: 转到注册
auth.login-instead: 转到登录
auth.github-oauth: 使用 GitHub 账号继续
auth.gitea-oauth: 使用 Gitea 账号继续
error: 错误
header.menu.all: 全部
header.menu.new: 创建
header.menu.search: 搜索
header.menu.my-gists: 我的 Gists
header.menu.liked: Liked
header.menu.admin: 管理
header.menu.settings: 设置
header.menu.logout: 登出
header.menu.register: 注册
header.menu.login: 登录
header.menu.light: 亮色
header.menu.dark: 暗色
header.menu.system: 系统
footer.powered-by: 由 %s 强力驱动
pagination.older: 更早
pagination.newer: 更新
pagination.previous: 上一页
pagination.next: 下一页
admin.admin_panel: 管理面板
admin.general: 通用
admin.users: 用户
admin.gists: Gists
admin.configuration: 配置
admin.versions: 版本
admin.ssh_keys: SSH 密钥
admin.stats: 状态
admin.actions: 动作
admin.actions.sync-fs: 从文件系统同步 Gist
admin.actions.sync-db: 从数据库同步 Gist
admin.actions.git-gc: 对 Git 仓库执行垃圾回收
admin.id: ID
admin.user: 用户
admin.delete: 删除
admin.created_at: 创建于
admin.config-link: 此配置可通过 YAML 配置和/或环境变量进行 %s 。
admin.config-link-overriden: 覆盖
admin.disable-signup: 禁用注册
admin.disable-signup_help: 阻止创建新的账号。
admin.require-login: 要求登录
admin.require-login_help: 强制用户登录后才能查看 Gist。
admin.disable-login: 禁用登录表单
admin.disable-login_help: 禁止使用登录表单进行登录以强制通过 OAuth 提供方登录。
admin.disable-gravatar: 禁用 Gravatar
admin.disable-gravatar_help: 停止使用 Gravatar 作为头像提供方。
admin.users.delete_confirm: 你想要删除此用户吗?
admin.gists.title: 标题
admin.gists.private: 私有?
admin.gists.nb-files: 文件数
admin.gists.nb-likes: 喜欢数
admin.gists.delete_confirm: 你想要删除此 Gist 吗?

View File

@@ -140,7 +140,7 @@ func processLogin(ctx echo.Context) error {
func oauthCallback(ctx echo.Context) error {
user, err := gothic.CompleteUserAuth(ctx.Response(), ctx.Request())
if err != nil {
return errorRes(400, "Cannot complete user auth", err)
return errorRes(400, "Cannot complete user auth: "+err.Error(), err)
}
currUser := getUserLogged(ctx)

View File

@@ -18,6 +18,8 @@ import (
func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
currUser := getUserLogged(ctx)
userName := ctx.Param("user")
gistName := ctx.Param("gistname")
@@ -27,6 +29,13 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
if err != nil {
return notFound("Gist not found")
}
if gist.Private == 2 {
if currUser == nil || currUser.ID != gist.UserID {
return notFound("Gist not found")
}
}
setData(ctx, "gist", gist)
if config.C.SshGit {
@@ -72,7 +81,7 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
}
setData(ctx, "nbCommits", nbCommits)
if currUser := getUserLogged(ctx); currUser != nil {
if currUser != nil {
hasLiked, err := currUser.HasLiked(gist)
if err != nil {
return errorRes(500, "Cannot get user like status", err)

View File

@@ -218,6 +218,7 @@ func NewServer(isDev bool) *Server {
g1.DELETE("/settings/account", accountDeleteProcess, logged)
g1.POST("/settings/ssh-keys", sshKeysProcess, logged)
g1.DELETE("/settings/ssh-keys/:id", sshKeysDelete, logged)
g1.PUT("/settings/password", passwordProcess, logged)
g2 := g1.Group("/admin-panel")
{

View File

@@ -21,6 +21,7 @@ func userSettings(ctx echo.Context) error {
setData(ctx, "email", user.Email)
setData(ctx, "sshKeys", keys)
setData(ctx, "hasPassword", user.Password != "")
setData(ctx, "htmlTitle", "Settings")
return html(ctx, "settings.html")
}
@@ -110,3 +111,31 @@ func sshKeysDelete(ctx echo.Context) error {
addFlash(ctx, "SSH key deleted", "success")
return redirect(ctx, "/settings")
}
func passwordProcess(ctx echo.Context) error {
user := getUserLogged(ctx)
dto := new(db.UserDTO)
if err := ctx.Bind(dto); err != nil {
return errorRes(400, "Cannot bind data", err)
}
dto.Username = user.Username
if err := ctx.Validate(dto); err != nil {
addFlash(ctx, validationMessages(&err), "error")
return html(ctx, "settings.html")
}
password, err := argon2id.hash(dto.Password)
if err != nil {
return errorRes(500, "Cannot hash password", err)
}
user.Password = password
if err = user.Update(); err != nil {
return errorRes(500, "Cannot update password", err)
}
addFlash(ctx, "Password updated", "success")
return redirect(ctx, "/settings")
}

View File

@@ -265,8 +265,16 @@ func (a Argon2ID) hash(plain string) (string, error) {
}
func (a Argon2ID) verify(plain, hash string) (bool, error) {
if hash == "" {
return false, nil
}
hashParts := strings.Split(hash, "$")
if len(hashParts) != 6 {
return false, errors.New("invalid hash")
}
_, err := fmt.Sscanf(hashParts[3], "m=%d,t=%d,p=%d", &a.memory, &a.time, &a.threads)
if err != nil {
return false, err

4
public/style.css vendored
View File

@@ -152,3 +152,7 @@ dl.dl-config dt {
dl.dl-config dd {
@apply ml-1 col-span-2 break-words;
}
.markdown-body {
@apply dark:bg-gray-900 !important;
}

View File

@@ -105,15 +105,15 @@
</svg>
</div>
<div class="hidden relative sm:inline-block text-left">
<div id="user-menu" class="hidden w-32 font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-600 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none">
<div id="user-menu" class="hidden w-max font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-600 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none">
<div class="py-1" role="none">
<a href="{{ $.c.ExternalUrl }}/{{ .userLogged.Username }}" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<a href="{{ $.c.ExternalUrl }}/{{ .userLogged.Username }}" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 pr-6 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
</svg>
{{ .locale.Tr "header.menu.my-gists" }}
</a>
<a href="{{ $.c.ExternalUrl }}/{{ .userLogged.Username }}/liked" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<a href="{{ $.c.ExternalUrl }}/{{ .userLogged.Username }}/liked" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 pr-6 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
<path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
</svg>
@@ -122,7 +122,7 @@
</div>
{{ if .userLogged.IsAdmin }}
<div class="py-1" role="none">
<a href="{{ $.c.ExternalUrl }}/admin-panel" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<a href="{{ $.c.ExternalUrl }}/admin-panel" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 pr-6 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
</svg>
@@ -131,14 +131,14 @@
</div>
{{ end }}
<div class="py-1" role="none">
<a href="{{ $.c.ExternalUrl }}/settings" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<a href="{{ $.c.ExternalUrl }}/settings" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 pr-6 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
{{ .locale.Tr "header.menu.settings" }}
</a>
<a href="{{ $.c.ExternalUrl }}/logout" class="dark:text-rose-400 text-rose-500 group flex items-center px-3 py-1.5 text-sm w-full hover:text-rose-600 dark:hover:text-rose-500" role="menuitem" tabindex="-1">
<a href="{{ $.c.ExternalUrl }}/logout" class="dark:text-rose-400 text-rose-500 group flex items-center px-3 py-1.5 pr-6 text-sm w-full hover:text-rose-600 dark:hover:text-rose-500" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 dark:text-rose-400 text-rose-500 group-hover:text-rose-600 dark:group-hover:text-rose-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
</svg>

View File

@@ -31,7 +31,7 @@
</svg>
</button>
</div>
<div id="sort-gists-dropdown" class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-200 dark:divide-gray-700 rounded-md rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 shadow-lg ring-1 ring-white dark:ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div id="sort-gists-dropdown" class="hidden absolute right-0 z-10 mt-2 w-max origin-top-right divide-y divide-gray-200 dark:divide-gray-700 rounded-md rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 shadow-lg ring-1 ring-white dark:ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div class="" role="none">
<a href="{{ $.c.ExternalUrl }}/{{ .urlPage }}?sort=created&order=desc{{.searchQueryUrl}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-t-md" role="menuitem">
{{ .locale.Tr "gist.list.order-by-desc" }} {{ .locale.Tr "gist.list.sort-by-created" }}

View File

@@ -66,7 +66,7 @@
</svg>
</button>
<div id="gist-menu-visibility" class="hidden absolute right-0 z-10 mt-2 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="gist-visibility-menu-button">
<div class="rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none" role="none">
<div class="rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none" role="none" style="word-break: keep-all">
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-btntext="{{ .locale.Tr "gist.new.create-public-button" }}" data-visibility="0" role="menuitem">{{ .locale.Tr "gist.public" }}</span>
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-btntext="{{ .locale.Tr "gist.new.create-unlisted-button" }}" data-visibility="1" role="menuitem">{{ .locale.Tr "gist.unlisted" }}</span>
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-btntext="{{ .locale.Tr "gist.new.create-private-button" }}" data-visibility="2" role="menuitem">{{ .locale.Tr "gist.private" }}</span>

View File

@@ -90,7 +90,43 @@
</div>
</div>
</div>
<div class="sm:grid grid-cols-2 gap-x-4 md:gap-x-8">
<div class="sm:grid grid-cols-3 gap-x-4 md:gap-x-8">
<div class="w-full">
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
{{if .hasPassword}}
{{ .locale.Tr "settings.change-password" }}
{{else}}
{{ .locale.Tr "settings.create-password" }}
{{end}}
</h2>
<h3 class="text-sm text-gray-600 dark:text-gray-400 italic mb-4">
{{if .hasPassword}}
{{ .locale.Tr "settings.change-password-help" }}
{{else}}
{{ .locale.Tr "settings.create-password-help" }}
{{end}}
</h3>
<form class="space-y-6" action="/settings/password" method="post">
<div>
<label for="password-change" class="block text-sm font-medium text-slate-700 dark:text-slate-300"> {{ .locale.Tr "settings.password-label-title" }} </label>
<div class="mt-1">
<input id="password-change" name="password" type="password" required autocomplete="off" class="dark:bg-gray-800 appearance-none block w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md shadow-sm placeholder-gray-600 dark:placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm">
</div>
</div>
<input type="hidden" name="_method" value="PUT">
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
{{if .hasPassword}}
{{ .locale.Tr "settings.change-password" }}
{{else}}
{{ .locale.Tr "settings.create-password" }}
{{end}}
</button>
{{ .csrfHtml }}
</form>
</div>
</div>
<div class="w-full">
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">