feat(backend): add data export

This commit is contained in:
Sam 2023-03-15 15:24:51 +01:00
parent ded9d06e4a
commit 15109819df
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
13 changed files with 559 additions and 4 deletions

View file

@ -221,10 +221,26 @@ func (db *DB) DeleteMemberAvatar(ctx context.Context, memberID xid.ID, hash stri
return errors.Wrap(err, "deleting webp avatar")
}
err = db.minio.RemoveObject(ctx, db.minioBucket, "/members/"+memberID.String()+"/"+hash+".webp", minio.RemoveObjectOptions{})
err = db.minio.RemoveObject(ctx, db.minioBucket, "/members/"+memberID.String()+"/"+hash+".jpg", minio.RemoveObjectOptions{})
if err != nil {
return errors.Wrap(err, "deleting jpeg avatar")
}
return nil
}
func (db *DB) UserAvatar(ctx context.Context, userID xid.ID, hash string) (io.ReadCloser, error) {
obj, err := db.minio.GetObject(ctx, db.minioBucket, "/users/"+userID.String()+"/"+hash+".webp", minio.GetObjectOptions{})
if err != nil {
return nil, errors.Wrap(err, "getting object")
}
return obj, nil
}
func (db *DB) MemberAvatar(ctx context.Context, memberID xid.ID, hash string) (io.ReadCloser, error) {
obj, err := db.minio.GetObject(ctx, db.minioBucket, "/members/"+memberID.String()+"/"+hash+".webp", minio.GetObjectOptions{})
if err != nil {
return nil, errors.Wrap(err, "getting object")
}
return obj, nil
}

104
backend/db/export.go Normal file
View file

@ -0,0 +1,104 @@
package db
import (
"bytes"
"context"
"time"
"emperror.dev/errors"
"github.com/georgysavva/scany/pgxscan"
"github.com/jackc/pgx/v4"
"github.com/minio/minio-go/v7"
"github.com/rs/xid"
)
type DataExport struct {
ID int64
UserID xid.ID
Filename string
CreatedAt time.Time
}
func (de DataExport) Path() string {
return "/exports/" + de.UserID.String() + "/" + de.Filename + ".zip"
}
const ErrNoExport = errors.Sentinel("no data export exists")
const KeepExportTime = 7 * 24 * time.Hour
func (db *DB) UserExport(ctx context.Context, userID xid.ID) (de DataExport, err error) {
sql, args, err := sq.Select("*").
From("data_exports").
Where("user_id = ?", userID).
OrderBy("id DESC").
Limit(1).ToSql()
if err != nil {
return de, errors.Wrap(err, "building query")
}
err = pgxscan.Get(ctx, db, &de, sql, args...)
if err != nil {
if errors.Cause(err) == pgx.ErrNoRows {
return de, ErrNoExport
}
return de, errors.Wrap(err, "executing sql")
}
return de, nil
}
const recentExport = 24 * time.Hour
func (db *DB) HasRecentExport(ctx context.Context, userID xid.ID) (hasExport bool, err error) {
err = db.QueryRow(ctx,
"SELECT EXISTS(SELECT * FROM data_exports WHERE user_id = $1 AND created_at > $2)",
userID, time.Now().Add(-recentExport)).Scan(&hasExport)
if err != nil {
return false, errors.Wrap(err, "executing query")
}
return hasExport, nil
}
func (db *DB) CreateExport(ctx context.Context, userID xid.ID, filename string, file *bytes.Buffer) (de DataExport, err error) {
de = DataExport{
UserID: userID,
Filename: filename,
}
_, err = db.minio.PutObject(ctx, db.minioBucket, de.Path(), file, int64(file.Len()), minio.PutObjectOptions{
ContentType: "application/zip",
})
if err != nil {
return de, errors.Wrap(err, "writing export file")
}
sql, args, err := sq.Insert("data_exports").Columns("user_id", "filename").Values(userID, filename).ToSql()
if err != nil {
return de, errors.Wrap(err, "building query")
}
pgxscan.Get(ctx, db, &de, sql, args...)
if err != nil {
return de, errors.Wrap(err, "executing sql")
}
return de, nil
}
func (db *DB) DeleteExport(ctx context.Context, de DataExport) (err error) {
sql, args, err := sq.Delete("data_exports").Where("id = ?", de.ID).ToSql()
if err != nil {
return errors.Wrap(err, "building query")
}
err = db.minio.RemoveObject(ctx, db.minioBucket, de.Path(), minio.RemoveObjectOptions{})
if err != nil {
return errors.Wrap(err, "deleting export zip")
}
_, err = db.Exec(ctx, sql, args...)
if err != nil {
return errors.Wrap(err, "executing sql")
}
return nil
}

View file

@ -61,7 +61,7 @@ func (db *DB) UserMember(ctx context.Context, userID xid.ID, memberRef string) (
// UserMembers returns all of a user's members, sorted by name.
func (db *DB) UserMembers(ctx context.Context, userID xid.ID) (ms []Member, err error) {
sql, args, err := sq.Select("id", "user_id", "name", "display_name", "bio", "avatar", "names", "pronouns").
sql, args, err := sq.Select("*").
From("members").Where("user_id = ?", userID).
OrderBy("name", "id").ToSql()
if err != nil {