tmp 1
This commit is contained in:
parent
f74a4779aa
commit
5bdb7fb0b0
@ -21,6 +21,7 @@ func main() {
|
|||||||
|
|
||||||
pg := postgres.New(ctx, "postgres://draincloud:draincloud@localhost:5432/draincloud?sslmode=disable")
|
pg := postgres.New(ctx, "postgres://draincloud:draincloud@localhost:5432/draincloud?sslmode=disable")
|
||||||
|
|
||||||
|
// TODO move cron on a separate job (k8s cronjob / docker cron)
|
||||||
cleanupSessionsCron := cleanupsessions.New(pg)
|
cleanupSessionsCron := cleanupsessions.New(pg)
|
||||||
cleanupSessionsCron.Run(ctx)
|
cleanupSessionsCron.Run(ctx)
|
||||||
|
|
||||||
|
3
go.mod
3
go.mod
@ -7,8 +7,10 @@ require (
|
|||||||
github.com/fatih/color v1.17.0
|
github.com/fatih/color v1.17.0
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
|
github.com/google/uuid v1.4.0
|
||||||
github.com/jackc/pgx/v5 v5.7.1
|
github.com/jackc/pgx/v5 v5.7.1
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
|
github.com/nats-io/nats.go v1.37.0
|
||||||
github.com/spf13/viper v1.19.0
|
github.com/spf13/viper v1.19.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.28.0
|
||||||
@ -38,7 +40,6 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/nats-io/nats.go v1.37.0 // indirect
|
|
||||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -39,6 +39,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
|||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
@ -32,7 +32,7 @@ func New(
|
|||||||
authGroup := mux.Group("/auth")
|
authGroup := mux.Group("/auth")
|
||||||
{
|
{
|
||||||
authGroup.POST("/register", d.Register)
|
authGroup.POST("/register", d.Register)
|
||||||
authGroup.POST("/login", d.Login)
|
authGroup.POST("/logon", d.Login)
|
||||||
}
|
}
|
||||||
|
|
||||||
filesGroup := mux.Group("/files")
|
filesGroup := mux.Group("/files")
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models/files"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models/files"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FilesEngine struct {
|
type FilesEngine struct {
|
||||||
@ -25,7 +26,7 @@ func NewFilesEngine(
|
|||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Name string
|
Name string
|
||||||
UserID int64
|
UserID uuid.UUID
|
||||||
Ext string
|
Ext string
|
||||||
Type string
|
Type string
|
||||||
Size int64
|
Size int64
|
||||||
@ -36,14 +37,14 @@ type File struct {
|
|||||||
func (e *FilesEngine) SaveFile(
|
func (e *FilesEngine) SaveFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
file File,
|
file File,
|
||||||
) (int64, error) {
|
) (uuid.UUID, error) {
|
||||||
fileID, err := e.metaStorage.SaveMetadata(ctx, files.FileMetadata{})
|
fileID, err := e.metaStorage.SaveMetadata(ctx, files.FileMetadata{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, fmt.Errorf("failed to create new file metadata: %w", err)
|
return uuid.Nil, fmt.Errorf("failed to create new file metadata: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = e.blobStorage.SaveBlob(ctx, fileID, file.Data); err != nil {
|
if err = e.blobStorage.SaveBlob(ctx, fileID, file.Data); err != nil {
|
||||||
return -1, fmt.Errorf("failed to save file data: %w", err)
|
return uuid.Nil, fmt.Errorf("failed to save file data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileID, nil
|
return fileID, nil
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
||||||
auditmodels "git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models/audit"
|
auditmodels "git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models/audit"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models/files"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models/files"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Database interface {
|
type Database interface {
|
||||||
@ -14,13 +15,13 @@ type Database interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthStorage interface {
|
type AuthStorage interface {
|
||||||
AddUser(ctx context.Context, login string, username string, passwordHash []byte) (int64, error)
|
AddUser(ctx context.Context, login string, username string, passwordHash []byte) (uuid.UUID, error)
|
||||||
GetUserByLogin(ctx context.Context, login string) (*models.User, error)
|
GetUserByLogin(ctx context.Context, login string) (*models.User, error)
|
||||||
GetUserByID(ctx context.Context, id uint64) (*models.User, error)
|
GetUserByID(ctx context.Context, id uuid.UUID) (*models.User, error)
|
||||||
|
|
||||||
AddSession(ctx context.Context, ses *models.Session) (int64, error)
|
AddSession(ctx context.Context, ses *models.Session) (uuid.UUID, error)
|
||||||
GetSession(ctx context.Context, sessionToken string) (*models.Session, error)
|
GetSession(ctx context.Context, sessionToken string) (*models.Session, error)
|
||||||
RemoveSession(ctx context.Context, id int64) error
|
RemoveSession(ctx context.Context, id uuid.UUID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthAuditLogStorage interface {
|
type AuthAuditLogStorage interface {
|
||||||
@ -28,11 +29,11 @@ type AuthAuditLogStorage interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MetaStorage interface {
|
type MetaStorage interface {
|
||||||
SaveMetadata(ctx context.Context, meta files.FileMetadata) (int64, error)
|
SaveMetadata(ctx context.Context, meta files.FileMetadata) (uuid.UUID, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlobStorage interface {
|
type BlobStorage interface {
|
||||||
GetFile(ctx context.Context, id int64) (*os.File, error)
|
GetFile(ctx context.Context, id uuid.UUID) (*os.File, error)
|
||||||
SaveBlob(ctx context.Context, id int64, data []byte) error
|
SaveBlob(ctx context.Context, id uuid.UUID, data []byte) error
|
||||||
DeleteFile(ctx context.Context, id int64) error
|
DeleteFile(ctx context.Context, id uuid.UUID) error
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
ID int64
|
ID uuid.UUID
|
||||||
SessionToken string
|
SessionToken string
|
||||||
CsrfToken string
|
CsrfToken string
|
||||||
UserID int64
|
UserID uuid.UUID
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
ExpiredAt time.Time
|
ExpiredAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64
|
ID uuid.UUID
|
||||||
Username string
|
Username string
|
||||||
Login string
|
Login string
|
||||||
PasswordHash []byte
|
PasswordHash []byte
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package files
|
package files
|
||||||
|
|
||||||
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
type FileMetadata struct {
|
type FileMetadata struct {
|
||||||
Id int64
|
Id uuid.UUID
|
||||||
Name string
|
Name string
|
||||||
UserID int64
|
UserID int64
|
||||||
Ext string
|
Ext string
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/closer"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/closer"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
)
|
)
|
||||||
@ -39,11 +40,11 @@ type dbtx interface {
|
|||||||
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
|
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) AddUser(ctx context.Context, login string, username string, passwordHash []byte) (int64, error) {
|
func (d *Database) AddUser(ctx context.Context, login string, username string, passwordHash []byte) (uuid.UUID, error) {
|
||||||
return addUser(ctx, d.db, login, username, passwordHash)
|
return addUser(ctx, d.db, login, username, passwordHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) GetUserByID(ctx context.Context, id uint64) (*models.User, error) {
|
func (d *Database) GetUserByID(ctx context.Context, id uuid.UUID) (*models.User, error) {
|
||||||
return getUserByID(ctx, d.db, id)
|
return getUserByID(ctx, d.db, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ func (d *Database) GetUserByLogin(ctx context.Context, login string) (*models.Us
|
|||||||
return getUserByLogin(ctx, d.db, login)
|
return getUserByLogin(ctx, d.db, login)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) AddSession(ctx context.Context, ses *models.Session) (int64, error) {
|
func (d *Database) AddSession(ctx context.Context, ses *models.Session) (uuid.UUID, error) {
|
||||||
return addSession(ctx, d.db, ses)
|
return addSession(ctx, d.db, ses)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,9 +65,9 @@ func (d *Database) GetSession(ctx context.Context, sessionToken string) (*models
|
|||||||
row := d.db.QueryRow(ctx, stmt, sessionToken)
|
row := d.db.QueryRow(ctx, stmt, sessionToken)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
id int64
|
id uuid.UUID
|
||||||
sesToken, csrfToken string
|
sesToken, csrfToken string
|
||||||
userID int64
|
userID uuid.UUID
|
||||||
createdAt sql.NullTime
|
createdAt sql.NullTime
|
||||||
expiredAt sql.NullTime
|
expiredAt sql.NullTime
|
||||||
)
|
)
|
||||||
@ -85,7 +86,7 @@ func (d *Database) GetSession(ctx context.Context, sessionToken string) (*models
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) RemoveSession(ctx context.Context, id int64) error {
|
func (d *Database) RemoveSession(ctx context.Context, id uuid.UUID) error {
|
||||||
const stmt = `DELETE FROM sessions WHERE id = $1;`
|
const stmt = `DELETE FROM sessions WHERE id = $1;`
|
||||||
_, err := d.db.Exec(ctx, stmt, id)
|
_, err := d.db.Exec(ctx, stmt, id)
|
||||||
return err
|
return err
|
||||||
@ -98,20 +99,20 @@ func (d *Database) RemoveExpiredSessions(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func addUser(ctx context.Context, conn dbtx, login string, username string, passwordHash []byte) (int64, error) {
|
func addUser(ctx context.Context, conn dbtx, login string, username string, passwordHash []byte) (uuid.UUID, error) {
|
||||||
const stmt = `INSERT INTO users (login,username,password)
|
const stmt = `INSERT INTO users (login,username,password)
|
||||||
VALUES ($1,$2,$3) RETURNING id`
|
VALUES ($1,$2,$3) RETURNING id`
|
||||||
|
|
||||||
row := conn.QueryRow(ctx, stmt, login, username, passwordHash)
|
row := conn.QueryRow(ctx, stmt, login, username, passwordHash)
|
||||||
var id int64
|
var id uuid.UUID
|
||||||
if err := row.Scan(&id); err != nil {
|
if err := row.Scan(&id); err != nil {
|
||||||
return 0, fmt.Errorf("failed to insert user data into users table: %w", err)
|
return uuid.Nil, fmt.Errorf("failed to insert user data into users table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserByID(ctx context.Context, conn dbtx, id uint64) (*models.User, error) {
|
func getUserByID(ctx context.Context, conn dbtx, id uuid.UUID) (*models.User, error) {
|
||||||
const stmt = `SELECT * FROM users WHERE id = $1 LIMIT 1`
|
const stmt = `SELECT * FROM users WHERE id = $1 LIMIT 1`
|
||||||
u := new(models.User)
|
u := new(models.User)
|
||||||
row := conn.QueryRow(ctx, stmt, id)
|
row := conn.QueryRow(ctx, stmt, id)
|
||||||
@ -133,13 +134,13 @@ func getUserByLogin(ctx context.Context, conn dbtx, login string) (*models.User,
|
|||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addSession(ctx context.Context, conn dbtx, session *models.Session) (int64, error) {
|
func addSession(ctx context.Context, conn dbtx, session *models.Session) (uuid.UUID, error) {
|
||||||
const stmt = `INSERT INTO sessions (session_token, csrf_token, user_id,
|
const stmt = `INSERT INTO sessions (session_token, csrf_token, user_id,
|
||||||
created_at, expired_at) VALUES ($1, $2, $3, $4, $5) RETURNING id;`
|
created_at, expired_at) VALUES ($1, $2, $3, $4, $5) RETURNING id;`
|
||||||
var id int64
|
var id uuid.UUID
|
||||||
row := conn.QueryRow(ctx, stmt, session.SessionToken, session.CsrfToken, session.UserID, session.CreatedAt, session.ExpiredAt)
|
row := conn.QueryRow(ctx, stmt, session.SessionToken, session.CsrfToken, session.UserID, session.CreatedAt, session.ExpiredAt)
|
||||||
if err := row.Scan(&id); err != nil {
|
if err := row.Scan(&id); err != nil {
|
||||||
return 0, fmt.Errorf("failed to insert new session: %w", err)
|
return uuid.Nil, fmt.Errorf("failed to insert new session: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return id, nil
|
return id, nil
|
||||||
|
@ -4,7 +4,7 @@ SELECT 'up SQL query';
|
|||||||
|
|
||||||
-- Users as auth data
|
-- Users as auth data
|
||||||
create table if not exists users (
|
create table if not exists users (
|
||||||
id bigserial primary key,
|
id uuid primary key,
|
||||||
username text default null,
|
username text default null,
|
||||||
login text not null unique,
|
login text not null unique,
|
||||||
password bytea not null,
|
password bytea not null,
|
||||||
@ -18,10 +18,10 @@ create index idx_users_username on users (username);
|
|||||||
|
|
||||||
-- Sessions and auth data
|
-- Sessions and auth data
|
||||||
create table sessions (
|
create table sessions (
|
||||||
id bigserial primary key,
|
id uuid primary key,
|
||||||
session_token varchar(200) not null unique,
|
session_token varchar(200) not null unique,
|
||||||
csrf_token varchar(200) not null unique,
|
csrf_token varchar(200) not null unique,
|
||||||
user_id bigserial references users(id),
|
user_id uuid references users(id),
|
||||||
created_at timestamp default current_timestamp,
|
created_at timestamp default current_timestamp,
|
||||||
expired_at timestamp not null
|
expired_at timestamp not null
|
||||||
);
|
);
|
||||||
@ -30,13 +30,13 @@ create index if not exists idx_sessions_session_token_csrf_token on sessions (se
|
|||||||
|
|
||||||
-- Files
|
-- Files
|
||||||
create table files_metadata (
|
create table files_metadata (
|
||||||
id bigserial primary key,
|
id uuid primary key,
|
||||||
name text not null,
|
name text not null,
|
||||||
fslink text not null,
|
fslink text not null,
|
||||||
size bigint not null,
|
size bigint not null,
|
||||||
ext text not null,
|
ext text not null,
|
||||||
owner_id bigint not null,
|
owner_id uuid not null,
|
||||||
parent_dir bigint not null,
|
parent_dir uuid not null,
|
||||||
created_at timestamptz default current_timestamp,
|
created_at timestamptz default current_timestamp,
|
||||||
updated_at timestamptz default null,
|
updated_at timestamptz default null,
|
||||||
deleted_at timestamptz default null
|
deleted_at timestamptz default null
|
||||||
@ -46,10 +46,10 @@ create index idx_fm_owner_id on files_metadata(owner_id);
|
|||||||
create index idx_fm_owner_id_parent_dir on files_metadata(owner_id, parent_dir);
|
create index idx_fm_owner_id_parent_dir on files_metadata(owner_id, parent_dir);
|
||||||
|
|
||||||
create table directories (
|
create table directories (
|
||||||
id bigserial primary key,
|
id uuid primary key,
|
||||||
name text not null,
|
name text not null,
|
||||||
owner_id bigint not null,
|
owner_id uuid not null,
|
||||||
parent_dir bigint not null,
|
parent_dir uuid not null,
|
||||||
created_at timestamptz default current_timestamp,
|
created_at timestamptz default current_timestamp,
|
||||||
updated_at timestamptz default null,
|
updated_at timestamptz default null,
|
||||||
deleted_at timestamptz default null
|
deleted_at timestamptz default null
|
||||||
@ -58,9 +58,9 @@ create table directories (
|
|||||||
create index idx_directories_owner_id_parent_dir on directories(owner_id, parent_dir);
|
create index idx_directories_owner_id_parent_dir on directories(owner_id, parent_dir);
|
||||||
|
|
||||||
create table directory_users_access (
|
create table directory_users_access (
|
||||||
id bigserial primary key,
|
id uuid primary key,
|
||||||
dir_id bigint not null,
|
dir_id uuid not null,
|
||||||
user_id bigint not null,
|
user_id uuid not null,
|
||||||
assess_flag integer,
|
assess_flag integer,
|
||||||
created_at timestamptz default current_timestamp,
|
created_at timestamptz default current_timestamp,
|
||||||
updated_at timestamptz default null
|
updated_at timestamptz default null
|
||||||
@ -68,8 +68,4 @@ create table directory_users_access (
|
|||||||
|
|
||||||
create index idx_dua_owner_id_parent_dir on directories(owner_id, parent_dir);
|
create index idx_dua_owner_id_parent_dir on directories(owner_id, parent_dir);
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
SELECT 'down SQL query';
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user