mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-06 11:39:42 -06:00
Libraries
This commit is contained in:
@@ -5,8 +5,8 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/shroff/phylum/server/internal/library"
|
||||
"github.com/shroff/phylum/server/internal/sql"
|
||||
"github.com/shroff/phylum/server/internal/user"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -35,7 +35,7 @@ func SetupCommand() {
|
||||
flags.String("database-url", "postgres://phylum:phylum@localhost:5432/phylum", "Database URL or DSN")
|
||||
viper.BindPFlag("database_url", flags.Lookup("database-url"))
|
||||
|
||||
var conn *pgx.Conn
|
||||
var db *sql.DbHandler
|
||||
|
||||
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||
workDir := viper.GetString("working_dir")
|
||||
@@ -51,27 +51,20 @@ func SetupCommand() {
|
||||
}
|
||||
|
||||
dsn := viper.GetString("database_url")
|
||||
config, err := pgx.ParseConfig(dsn)
|
||||
|
||||
if viper.GetBool("trace_sql") {
|
||||
config.Tracer = phylumTracer{}
|
||||
}
|
||||
|
||||
var err error
|
||||
db, err = sql.NewDb(dsn, debug && viper.GetBool("trace_sql"))
|
||||
if err != nil {
|
||||
logrus.Fatal("Unable to parse db connection String: " + err.Error())
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
conn, err = pgx.ConnectConfig(context.Background(), config)
|
||||
if err != nil {
|
||||
logrus.Fatal("Unable to connect to database: " + err.Error())
|
||||
}
|
||||
libraryManager = library.NewManager(conn)
|
||||
userManager = user.NewManager(conn)
|
||||
libraryManager = library.NewManager(db)
|
||||
userManager = user.NewManager(db)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if conn != nil {
|
||||
if db != nil {
|
||||
logrus.Info("Closing datbase connection")
|
||||
conn.Close(context.Background())
|
||||
db.Close(context.Background())
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -81,15 +74,3 @@ func SetupCommand() {
|
||||
}...)
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
||||
type phylumTracer struct {
|
||||
}
|
||||
|
||||
func (p phylumTracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
|
||||
logrus.Trace(data)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (p phylumTracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
|
||||
logrus.Trace(data)
|
||||
}
|
||||
|
||||
@@ -22,24 +22,36 @@ func setupLibraryCommand() *cobra.Command {
|
||||
|
||||
func setupLibraryCreateCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "create name",
|
||||
Short: "Create Root Folder",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Use: "create owner display-name",
|
||||
Short: "Create Library",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := libraryManager.Create(context.Background(), uuid.New(), args[0]); err != nil {
|
||||
id := uuid.New()
|
||||
username := args[0]
|
||||
user, err := userManager.FindUser(context.Background(), username)
|
||||
if err != nil {
|
||||
logrus.Fatal("User not found: " + username)
|
||||
}
|
||||
name := args[1]
|
||||
if err := libraryManager.Create(context.Background(), id, user.ID, name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
logrus.Info("Created " + id.String())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setupLibraryDeleteCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "delete name",
|
||||
Short: "Delete Root Folder",
|
||||
Use: "delete id",
|
||||
Short: "Delete Library",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := libraryManager.Delete(context.Background(), args[0]); err != nil {
|
||||
id, err := uuid.Parse(args[0])
|
||||
if err != nil {
|
||||
logrus.Fatal("Not an ID: " + args[0])
|
||||
}
|
||||
if err := libraryManager.Delete(context.Background(), id); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -17,9 +17,9 @@ import (
|
||||
)
|
||||
|
||||
type Library struct {
|
||||
queries sql.Queries
|
||||
root uuid.UUID
|
||||
cs storage.Storage
|
||||
db *sql.DbHandler
|
||||
root uuid.UUID
|
||||
cs storage.Storage
|
||||
}
|
||||
|
||||
func (l Library) Open(id uuid.UUID, flag int) (io.ReadWriteCloser, error) {
|
||||
@@ -27,13 +27,13 @@ func (l Library) Open(id uuid.UUID, flag int) (io.ReadWriteCloser, error) {
|
||||
}
|
||||
|
||||
func (l Library) ReadDir(ctx context.Context, id uuid.UUID, includeRoot bool, recursive bool) ([]sql.ReadDirRow, error) {
|
||||
return l.queries.ReadDir(ctx, sql.ReadDirParams{ID: id, IncludeRoot: includeRoot, Recursive: recursive})
|
||||
return l.db.Queries().ReadDir(ctx, sql.ReadDirParams{ID: id, IncludeRoot: includeRoot, Recursive: recursive})
|
||||
}
|
||||
|
||||
func (l Library) DeleteRecursive(ctx context.Context, id uuid.UUID, hardDelete bool) error {
|
||||
query := l.queries.DeleteRecursive
|
||||
query := l.db.Queries().DeleteRecursive
|
||||
if hardDelete {
|
||||
query = l.queries.HardDeleteRecursive
|
||||
query = l.db.Queries().HardDeleteRecursive
|
||||
}
|
||||
deleted, err := query(ctx, id)
|
||||
if err == nil && hardDelete {
|
||||
@@ -47,18 +47,18 @@ func (l Library) DeleteRecursive(ctx context.Context, id uuid.UUID, hardDelete b
|
||||
}
|
||||
|
||||
func (l Library) CreateResource(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string, dir bool) error {
|
||||
return l.queries.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: &parent, Name: name, Dir: dir})
|
||||
return l.db.Queries().CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: &parent, Name: name, Dir: dir})
|
||||
}
|
||||
|
||||
func (l Library) Move(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string) error {
|
||||
return l.queries.Rename(ctx, sql.RenameParams{ID: id, Parent: parent, Name: name})
|
||||
return l.db.Queries().Rename(ctx, sql.RenameParams{ID: id, Parent: parent, Name: name})
|
||||
}
|
||||
|
||||
func (l Library) UpdateContents(ctx context.Context, id uuid.UUID) (io.WriteCloser, error) {
|
||||
return l.cs.Open(id, os.O_CREATE|os.O_RDWR|os.O_TRUNC, func(h hash.Hash, len int, err error) {
|
||||
if err == nil {
|
||||
etag := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
err = l.queries.UpdateResourceContents(ctx, sql.UpdateResourceContentsParams{
|
||||
err = l.db.Queries().UpdateResourceContents(ctx, sql.UpdateResourceContentsParams{
|
||||
ID: id,
|
||||
Size: pgtype.Int4{Int32: int32(len), Valid: true},
|
||||
Etag: pgtype.Text{String: string(etag), Valid: true},
|
||||
@@ -79,7 +79,7 @@ func (l Library) ResourceByPath(ctx context.Context, path string) (sql.ResourceB
|
||||
segments = []string{}
|
||||
}
|
||||
|
||||
res, err := l.queries.ResourceByPath(ctx, sql.ResourceByPathParams{Search: segments, Root: l.root})
|
||||
res, err := l.db.Queries().ResourceByPath(ctx, sql.ResourceByPathParams{Search: segments, Root: l.root})
|
||||
if err != nil {
|
||||
return sql.ResourceByPathRow{}, fs.ErrNotExist
|
||||
}
|
||||
|
||||
@@ -5,47 +5,49 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/shroff/phylum/server/internal/sql"
|
||||
"github.com/shroff/phylum/server/internal/storage"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
queries sql.Queries
|
||||
cs storage.Storage
|
||||
db *sql.DbHandler
|
||||
cs storage.Storage
|
||||
}
|
||||
|
||||
func NewManager(conn *pgx.Conn) Manager {
|
||||
queries := *sql.New(conn)
|
||||
func NewManager(db *sql.DbHandler) Manager {
|
||||
cs, err := storage.NewLocalStorage("srv")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Manager{queries: queries, cs: cs}
|
||||
return Manager{db: db, cs: cs}
|
||||
}
|
||||
|
||||
func (b Manager) Get(ctx context.Context, name string) (*Library, error) {
|
||||
func (b Manager) Get(ctx context.Context, id uuid.UUID) (*Library, error) {
|
||||
// TODO: Permissions checks
|
||||
lib, err := b.queries.LibraryByName(ctx, name)
|
||||
lib, err := b.db.Queries().LibraryById(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Library{queries: b.queries, root: lib.ID, cs: b.cs}, nil
|
||||
return &Library{db: b.db, root: lib.ID, cs: b.cs}, nil
|
||||
}
|
||||
|
||||
func (b Manager) Create(ctx context.Context, id uuid.UUID, name string) error {
|
||||
if _, err := b.queries.LibraryByName(ctx, name); err == nil {
|
||||
return fmt.Errorf("root directory already exists: %s", name)
|
||||
} else {
|
||||
return b.queries.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: nil, Name: name, Dir: true})
|
||||
}
|
||||
func (b Manager) Create(ctx context.Context, id uuid.UUID, owner int32, displayName string) error {
|
||||
return b.db.RunInTx(ctx, func(q *sql.Queries) error {
|
||||
if err := q.CreateLibrary(ctx, sql.CreateLibraryParams{ID: id, Owner: owner, DisplayName: displayName}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := q.CreateResource(ctx, sql.CreateResourceParams{ID: id, Dir: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (b Manager) Delete(ctx context.Context, name string) error {
|
||||
if lib, err := b.Get(ctx, name); err == nil {
|
||||
func (b Manager) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
if lib, err := b.Get(ctx, id); err == nil {
|
||||
return lib.DeleteRecursive(ctx, lib.root, true)
|
||||
} else {
|
||||
return fmt.Errorf("root directory does not exist: %s", name)
|
||||
return fmt.Errorf("library does not exist: %s", id)
|
||||
}
|
||||
}
|
||||
|
||||
53
internal/sql/db_manager.go
Normal file
53
internal/sql/db_manager.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type DbHandler struct {
|
||||
conn *pgx.Conn
|
||||
queries *Queries
|
||||
}
|
||||
|
||||
func NewDb(dsn string, trace bool) (*DbHandler, error) {
|
||||
config, err := pgx.ParseConfig(dsn)
|
||||
if err != nil {
|
||||
return nil, errors.New("Unable to parse DSN: " + err.Error())
|
||||
}
|
||||
|
||||
if trace {
|
||||
config.Tracer = tracer{}
|
||||
}
|
||||
|
||||
conn, err := pgx.ConnectConfig(context.Background(), config)
|
||||
if err != nil {
|
||||
return nil, errors.New("Unable to connect to database: " + err.Error())
|
||||
}
|
||||
|
||||
logrus.Info("Connected to " + config.Database + " at " + config.Host + ":" + fmt.Sprint(config.Port))
|
||||
|
||||
return &DbHandler{
|
||||
conn: conn,
|
||||
queries: &Queries{db: conn},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d DbHandler) Queries() *Queries {
|
||||
return d.queries
|
||||
}
|
||||
|
||||
func (d DbHandler) RunInTx(ctx context.Context, fn func(*Queries) error) error {
|
||||
return pgx.BeginFunc(ctx, d.conn, func(tx pgx.Tx) error {
|
||||
q := d.queries.WithTx(tx)
|
||||
return fn(q)
|
||||
})
|
||||
}
|
||||
|
||||
func (d DbHandler) Close(ctx context.Context) {
|
||||
d.conn.Close(ctx)
|
||||
}
|
||||
47
internal/sql/libraries.sql.go
Normal file
47
internal/sql/libraries.sql.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.25.0
|
||||
// source: libraries.sql
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const createLibrary = `-- name: CreateLibrary :exec
|
||||
INSERT INTO libraries(
|
||||
id, owner, display_name
|
||||
) VALUES(
|
||||
$1, $2, $3
|
||||
)
|
||||
`
|
||||
|
||||
type CreateLibraryParams struct {
|
||||
ID uuid.UUID
|
||||
Owner int32
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
func (q *Queries) CreateLibrary(ctx context.Context, arg CreateLibraryParams) error {
|
||||
_, err := q.db.Exec(ctx, createLibrary, arg.ID, arg.Owner, arg.DisplayName)
|
||||
return err
|
||||
}
|
||||
|
||||
const libraryById = `-- name: LibraryById :one
|
||||
SELECT id, owner, display_name, deleted from libraries where id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) LibraryById(ctx context.Context, id uuid.UUID) (Library, error) {
|
||||
row := q.db.QueryRow(ctx, libraryById, id)
|
||||
var i Library
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Owner,
|
||||
&i.DisplayName,
|
||||
&i.Deleted,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -9,6 +9,13 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type Library struct {
|
||||
ID uuid.UUID
|
||||
Owner int32
|
||||
DisplayName string
|
||||
Deleted pgtype.Timestamp
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
ID uuid.UUID
|
||||
Parent *uuid.UUID
|
||||
@@ -22,7 +29,7 @@ type Resource struct {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID pgtype.Int4
|
||||
ID int32
|
||||
DisplayName string
|
||||
Username string
|
||||
PasswordHash string
|
||||
|
||||
@@ -126,27 +126,6 @@ func (q *Queries) HardDeleteRecursive(ctx context.Context, id uuid.UUID) ([]Reso
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const libraryByName = `-- name: LibraryByName :one
|
||||
SELECT id, parent, name, dir, created, modified, deleted, size, etag from resources WHERE deleted IS NULL AND parent IS NULL AND name = $1
|
||||
`
|
||||
|
||||
func (q *Queries) LibraryByName(ctx context.Context, name string) (Resource, error) {
|
||||
row := q.db.QueryRow(ctx, libraryByName, name)
|
||||
var i Resource
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Parent,
|
||||
&i.Name,
|
||||
&i.Dir,
|
||||
&i.Created,
|
||||
&i.Modified,
|
||||
&i.Deleted,
|
||||
&i.Size,
|
||||
&i.Etag,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const readDir = `-- name: ReadDir :many
|
||||
WITH RECURSIVE nodes(id, parent, name, dir, created, modified, size, etag, depth, path) AS (
|
||||
SELECT r.id, r.parent, r.name, r.dir, r.created, r.modified, r.size, r.etag, 0, ''::text
|
||||
|
||||
20
internal/sql/tracer.go
Normal file
20
internal/sql/tracer.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type tracer struct {
|
||||
}
|
||||
|
||||
func (t tracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
|
||||
logrus.Trace(data)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c tracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
|
||||
logrus.Trace(data)
|
||||
}
|
||||
@@ -3,21 +3,19 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/shroff/phylum/server/internal/sql"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
queries sql.Queries
|
||||
db *sql.DbHandler
|
||||
}
|
||||
|
||||
func NewManager(conn *pgx.Conn) Manager {
|
||||
queries := *sql.New(conn)
|
||||
return Manager{queries: queries}
|
||||
func NewManager(db *sql.DbHandler) Manager {
|
||||
return Manager{db: db}
|
||||
}
|
||||
|
||||
func (m Manager) CreateUser(ctx context.Context, username, displayName, passwordHash string) error {
|
||||
_, err := m.queries.CreateUser(ctx, sql.CreateUserParams{Username: username, DisplayName: displayName, PasswordHash: passwordHash})
|
||||
_, err := m.db.Queries().CreateUser(ctx, sql.CreateUserParams{Username: username, DisplayName: displayName, PasswordHash: passwordHash})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -25,5 +23,5 @@ func (m Manager) CreateUser(ctx context.Context, username, displayName, password
|
||||
}
|
||||
|
||||
func (m Manager) FindUser(ctx context.Context, username string) (sql.User, error) {
|
||||
return m.queries.UserByUsername(ctx, username)
|
||||
return m.db.Queries().UserByUsername(ctx, username)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
webdav "github.com/emersion/go-webdav"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/shroff/phylum/server/internal/library"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -25,17 +26,24 @@ func NewHandler(libraryManager library.Manager, prefix string) *handler {
|
||||
|
||||
func (h *handler) HandleRequest(c *gin.Context) {
|
||||
path := c.Params.ByName("path")
|
||||
libraryName := strings.TrimLeft(path, "/")
|
||||
if libraryName == "" {
|
||||
idStr := strings.TrimLeft(path, "/")
|
||||
if idStr == "" {
|
||||
// No path specified
|
||||
c.Writer.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
index := strings.Index(libraryName, "/")
|
||||
index := strings.Index(idStr, "/")
|
||||
if index != -1 {
|
||||
libraryName = libraryName[0:index]
|
||||
idStr = idStr[0:index]
|
||||
}
|
||||
library, err := h.libraryManager.Get(context.Background(), libraryName)
|
||||
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.Writer.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
library, err := h.libraryManager.Get(context.Background(), id)
|
||||
if err != nil {
|
||||
c.Writer.WriteHeader(404)
|
||||
return
|
||||
@@ -44,7 +52,7 @@ func (h *handler) HandleRequest(c *gin.Context) {
|
||||
webdavHandler := webdav.Handler{
|
||||
FileSystem: adapter{
|
||||
lib: library,
|
||||
prefix: h.prefix + "/" + libraryName,
|
||||
prefix: h.prefix + "/" + idStr,
|
||||
},
|
||||
}
|
||||
webdavHandler.ServeHTTP(c.Writer, c.Request)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/shroff/phylum/server/internal/cryptutil"
|
||||
"github.com/shroff/phylum/server/internal/library"
|
||||
"github.com/shroff/phylum/server/internal/user"
|
||||
@@ -66,24 +67,30 @@ func SetupHandler(r *gin.RouterGroup, libraryManager library.Manager, userManage
|
||||
|
||||
func (h *handler) HandleRequest(c *gin.Context) {
|
||||
path := c.Params.ByName("path")
|
||||
libraryName := strings.TrimLeft(path, "/")
|
||||
if libraryName == "" {
|
||||
idStr := strings.TrimLeft(path, "/")
|
||||
if idStr == "" {
|
||||
// No path specified
|
||||
c.Writer.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
index := strings.Index(libraryName, "/")
|
||||
index := strings.Index(idStr, "/")
|
||||
if index != -1 {
|
||||
libraryName = libraryName[0:index]
|
||||
idStr = idStr[0:index]
|
||||
}
|
||||
library, err := h.libraryManager.Get(context.Background(), libraryName)
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.Writer.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
library, err := h.libraryManager.Get(context.Background(), id)
|
||||
if err != nil {
|
||||
c.Writer.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
webdavHandler := webdav.Handler{
|
||||
Prefix: h.prefix + "/" + libraryName,
|
||||
Prefix: h.prefix + "/" + idStr,
|
||||
FileSystem: adapter{lib: library},
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
CREATE TABLE users(
|
||||
id SERIAL,
|
||||
id SERIAL PRIMARY KEY,
|
||||
display_name TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
deleted TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX unique_username ON users(username) WHERE deleted IS NULL;
|
||||
CREATE UNIQUE INDEX unique_username ON users(username);
|
||||
1
sql/migrations/003_libraries.down.sql
Normal file
1
sql/migrations/003_libraries.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE libraries;
|
||||
6
sql/migrations/003_libraries.up.sql
Normal file
6
sql/migrations/003_libraries.up.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE libraries(
|
||||
id uuid PRIMARY KEY,
|
||||
owner SERIAL NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
display_name TEXT NOT NULL,
|
||||
deleted TIMESTAMP
|
||||
);
|
||||
9
sql/queries/libraries.sql
Normal file
9
sql/queries/libraries.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
-- name: LibraryById :one
|
||||
SELECT * from libraries where id = $1;
|
||||
|
||||
-- name: CreateLibrary :exec
|
||||
INSERT INTO libraries(
|
||||
id, owner, display_name
|
||||
) VALUES(
|
||||
$1, $2, $3
|
||||
);
|
||||
@@ -1,9 +1,6 @@
|
||||
-- name: ResourceById :one
|
||||
SELECT * from resources WHERE id = $1;
|
||||
|
||||
-- name: LibraryByName :one
|
||||
SELECT * from resources WHERE deleted IS NULL AND parent IS NULL AND name = $1;
|
||||
|
||||
-- name: CreateResource :exec
|
||||
INSERT INTO resources(
|
||||
id, parent, name, dir, created, modified
|
||||
|
||||
Reference in New Issue
Block a user