add storage and model
This commit is contained in:
commit
7cd973f0a6
19 changed files with 618 additions and 0 deletions
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
12
.idea/dataSources.xml
Normal file
12
.idea/dataSources.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="status@localhost" uuid="abde68f9-bf9f-49bc-a087-097031ffa257">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://localhost:5432/status</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/status.iml" filepath="$PROJECT_DIR$/.idea/status.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/status.iml
Normal file
9
.idea/status.iml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
35
config/cfg.go
Normal file
35
config/cfg.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package config
|
||||
|
||||
import "os"
|
||||
|
||||
type (
|
||||
DBCfg struct {
|
||||
DatabaseURL string
|
||||
}
|
||||
|
||||
ServerCfg struct {
|
||||
SessionKey string
|
||||
Env string
|
||||
CertFile string
|
||||
CertKeyFile string
|
||||
}
|
||||
|
||||
Config struct {
|
||||
DB DBCfg
|
||||
Server ServerCfg
|
||||
}
|
||||
)
|
||||
|
||||
func New() *Config {
|
||||
return &Config{
|
||||
DB: DBCfg{
|
||||
DatabaseURL: os.Getenv("DATABASE_URL"),
|
||||
},
|
||||
Server: ServerCfg{
|
||||
SessionKey: os.Getenv("SESSION_KEY"),
|
||||
Env: os.Getenv("ENV"),
|
||||
CertFile: os.Getenv("CERT_FILE"),
|
||||
CertKeyFile: os.Getenv("CERT_KEY_FILE"),
|
||||
},
|
||||
}
|
||||
}
|
||||
85
generate.go
Normal file
85
generate.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const tpl = `// Code generated by go generate; DO NOT EDIT.
|
||||
|
||||
package {{ .Package }}
|
||||
|
||||
var {{ .Map }} = map[string]string{
|
||||
{{ range $constant, $content := .Files }}` + "\t" + `"{{ $constant }}": ` + "`{{ $content }}`" + `,
|
||||
{{ end }}}
|
||||
`
|
||||
|
||||
var bundleTpl = template.Must(template.New("").Parse(tpl))
|
||||
|
||||
type Bundle struct {
|
||||
Package string
|
||||
Map string
|
||||
Files map[string]string
|
||||
}
|
||||
|
||||
func (b *Bundle) Write(filename string) {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
bundleTpl.Execute(f, b)
|
||||
}
|
||||
|
||||
func NewBundle(pkg, mapName string) *Bundle {
|
||||
return &Bundle{
|
||||
Package: pkg,
|
||||
Map: mapName,
|
||||
Files: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func stripExtension(filename string) string {
|
||||
filename = strings.TrimSuffix(filename, path.Ext(filename))
|
||||
return strings.Replace(filename, " ", "_", -1)
|
||||
}
|
||||
|
||||
func readFile(filename string) []byte {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func glob(pattern string) []string {
|
||||
files, _ := filepath.Glob(pattern)
|
||||
for i := range files {
|
||||
if strings.Contains(files[i], "\\") {
|
||||
files[i] = filepath.ToSlash(files[i])
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func generateMap(target string, pkg string, mapName string, srcFiles []string) {
|
||||
bundle := NewBundle(pkg, mapName)
|
||||
for _, srcFile := range srcFiles {
|
||||
data := readFile(srcFile)
|
||||
filename := stripExtension(path.Base(srcFile))
|
||||
bundle.Files[filename] = string(data)
|
||||
}
|
||||
bundle.Write(target)
|
||||
}
|
||||
|
||||
func main() {
|
||||
generateMap(path.Join("storage", "sql.go"), "storage", "SqlMap", glob("storage/sql/*.sql"))
|
||||
//generateMap(path.Join("template", "html.go"), "template", "TplMap", glob("template/html/*.html"))
|
||||
}
|
||||
8
go.mod
Normal file
8
go.mod
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module status
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/lib/pq v1.10.4
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
||||
)
|
||||
11
go.sum
Normal file
11
go.sum
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
|
||||
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
21
main.go
Normal file
21
main.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//go:generate go run generate.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"status/config"
|
||||
"status/storage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := config.New()
|
||||
db, err := storage.InitDB(cfg.DB)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
storage.New(db)
|
||||
//data := storage.New(db)
|
||||
//log.Fatal(
|
||||
// server.Serve(data, cfg),
|
||||
//)
|
||||
}
|
||||
24
model/status.go
Normal file
24
model/status.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Id int64
|
||||
User string
|
||||
Content string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func (s Status) Validate() error {
|
||||
if len(s.Content) == 0 {
|
||||
return errors.New("content is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Status) Date() string {
|
||||
return s.CreatedAt.Format("2006-01-02")
|
||||
}
|
||||
37
model/user.go
Normal file
37
model/user.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Password string
|
||||
Hash []byte
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func (u User) Validate() error {
|
||||
if u.Name == "" {
|
||||
return errors.New("username is mandatory")
|
||||
}
|
||||
if u.Password == "" {
|
||||
return errors.New("password is mandatory")
|
||||
}
|
||||
match, _ := regexp.MatchString("^[a-z0-9-_]+$", u.Name)
|
||||
if !match {
|
||||
return errors.New("username should match [a-z0-9-_]")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u User) HashPassword() ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.MinCost)
|
||||
}
|
||||
|
||||
func (u User) CompareHashToPassword(hash []byte) error {
|
||||
return bcrypt.CompareHashAndPassword(hash, []byte(u.Password))
|
||||
}
|
||||
43
model/user_test.go
Normal file
43
model/user_test.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package model
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValidateUser(t *testing.T) {
|
||||
user := User{
|
||||
Name: "",
|
||||
Password: "password",
|
||||
}
|
||||
|
||||
user.Name = ""
|
||||
if err := user.Validate(); err == nil {
|
||||
t.Fatal("Empty username not allowed")
|
||||
}
|
||||
|
||||
user.Name = "miso"
|
||||
if err := user.Validate(); err != nil {
|
||||
t.Fatal("Regular characters allowed")
|
||||
}
|
||||
|
||||
user.Name = "m15o"
|
||||
if err := user.Validate(); err != nil {
|
||||
t.Fatal("Digits allowed")
|
||||
}
|
||||
|
||||
user.Name = "has space"
|
||||
if err := user.Validate(); err == nil {
|
||||
t.Fatal("Space is not allowed")
|
||||
}
|
||||
|
||||
user.Name = "M15O"
|
||||
if err := user.Validate(); err == nil {
|
||||
t.Fatal("Capital letters aren't allowed")
|
||||
}
|
||||
|
||||
characters := []string{"#", ":", "/", "@", "?"}
|
||||
for _, c := range characters {
|
||||
user.Name = c
|
||||
if err := user.Validate(); err == nil {
|
||||
t.Fatal("Special characters not allowed")
|
||||
}
|
||||
}
|
||||
}
|
||||
16
storage/db.go
Normal file
16
storage/db.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/lib/pq"
|
||||
"status/config"
|
||||
)
|
||||
|
||||
func InitDB(cfg config.DBCfg) (*sql.DB, error) {
|
||||
db, err := sql.Open("postgres", cfg.DatabaseURL)
|
||||
if err != nil {
|
||||
return db, err
|
||||
}
|
||||
Migrate(db)
|
||||
return db, err
|
||||
}
|
||||
51
storage/migration.go
Normal file
51
storage/migration.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const schemaVersion = 1
|
||||
|
||||
func Migrate(db *sql.DB) {
|
||||
var currentVersion int
|
||||
db.QueryRow(`SELECT version FROM schema_version`).Scan(¤tVersion)
|
||||
|
||||
fmt.Println("Current schema version:", currentVersion)
|
||||
fmt.Println("Latest schema version:", schemaVersion)
|
||||
|
||||
for version := currentVersion + 1; version <= schemaVersion; version++ {
|
||||
fmt.Println("Migrating to version:", version)
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
log.Fatal("[Migrate] ", err)
|
||||
}
|
||||
|
||||
rawSQL := SqlMap["schema_version_"+strconv.Itoa(version)]
|
||||
if rawSQL == "" {
|
||||
log.Fatalf("[Migrate] missing migration %d", version)
|
||||
}
|
||||
_, err = tx.Exec(string(rawSQL))
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("[Migrate] ", err)
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`delete from schema_version`); err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("[Migrate] ", err)
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`INSERT INTO schema_version (version) VALUES ($1)`, version); err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("[Migrate] ", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
log.Fatal("[Migrate] ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
storage/sql.go
Normal file
28
storage/sql.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
|
||||
package storage
|
||||
|
||||
var SqlMap = map[string]string{
|
||||
"schema_version_1": `-- create schema version table
|
||||
create table schema_version (
|
||||
version text not null
|
||||
);
|
||||
|
||||
-- create users table
|
||||
create table users
|
||||
(
|
||||
name text primary key CHECK (name <> ''),
|
||||
hash text not null CHECK (hash <> ''),
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
-- create posts status
|
||||
create table statuses
|
||||
(
|
||||
id serial primary key,
|
||||
author TEXT references users(name) NOT NULL,
|
||||
content VARCHAR(500) NOT NULL CHECK (content <> ''),
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
`,
|
||||
}
|
||||
21
storage/sql/schema_version_1.sql
Normal file
21
storage/sql/schema_version_1.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
-- create schema version table
|
||||
create table schema_version (
|
||||
version text not null
|
||||
);
|
||||
|
||||
-- create users table
|
||||
create table users
|
||||
(
|
||||
name text primary key CHECK (name <> ''),
|
||||
hash text not null CHECK (hash <> ''),
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
-- create posts status
|
||||
create table statuses
|
||||
(
|
||||
id serial primary key,
|
||||
author TEXT references users(name) NOT NULL,
|
||||
content VARCHAR(500) NOT NULL CHECK (content <> ''),
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
119
storage/status.go
Normal file
119
storage/status.go
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"status/model"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type statusQueryBuilder struct {
|
||||
where string
|
||||
limit string
|
||||
offset string
|
||||
}
|
||||
|
||||
func (p statusQueryBuilder) build() string {
|
||||
query := []string{`SELECT id, author, content, created_at from statuses`}
|
||||
if p.where != "" {
|
||||
query = append(query, `WHERE`, p.where)
|
||||
}
|
||||
query = append(query, `ORDER BY created_at desc`)
|
||||
if p.limit != "" {
|
||||
query = append(query, `LIMIT`, p.limit)
|
||||
}
|
||||
if p.offset != "" {
|
||||
query = append(query, `OFFSET`, p.offset)
|
||||
}
|
||||
return strings.Join(query, " ")
|
||||
}
|
||||
|
||||
func (s *Storage) populateStatus(rows *sql.Rows) (model.Status, error) {
|
||||
var status model.Status
|
||||
err := rows.Scan(&status.Id, &status.User, &status.Content, &status.CreatedAt)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (s *Storage) CreatePost(status model.Status) (int64, error) {
|
||||
var lid int64
|
||||
err := s.db.QueryRow(`INSERT INTO statuses (author, content) VALUES ($1, $2) RETURNING id`,
|
||||
status.User, status.Content).Scan(&lid)
|
||||
return lid, err
|
||||
}
|
||||
|
||||
func (s *Storage) StatusById(id int64) (model.Status, error) {
|
||||
var status model.Status
|
||||
err := s.db.QueryRow(
|
||||
`SELECT id, author, content from statuses WHERE id=$1`, id).Scan(
|
||||
&status.Id,
|
||||
&status.User,
|
||||
&status.Content,
|
||||
)
|
||||
return status, err
|
||||
}
|
||||
|
||||
func (s *Storage) StatusByUsername(user string, perPage int, page int64) ([]model.Status, bool, error) {
|
||||
rows, err := s.db.Query(statusQueryBuilder{
|
||||
where: `author = $1`,
|
||||
limit: strconv.Itoa(perPage + 1),
|
||||
offset: `$2`,
|
||||
}.build(), user, page*int64(perPage))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
var statuses []model.Status
|
||||
for rows.Next() {
|
||||
post, err := s.populateStatus(rows)
|
||||
if err != nil {
|
||||
return statuses, false, err
|
||||
}
|
||||
statuses = append(statuses, post)
|
||||
}
|
||||
if len(statuses) > perPage {
|
||||
return statuses[0:perPage], true, err
|
||||
}
|
||||
return statuses, false, err
|
||||
}
|
||||
|
||||
func (s *Storage) Statuses(page int64, perPage int) ([]model.Status, bool, error) {
|
||||
rows, err := s.db.Query(statusQueryBuilder{
|
||||
limit: strconv.Itoa(perPage + 1),
|
||||
offset: `$1`,
|
||||
}.build(), page*int64(perPage))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
var statuses []model.Status
|
||||
for rows.Next() {
|
||||
post, err := s.populateStatus(rows)
|
||||
if err != nil {
|
||||
return statuses, false, err
|
||||
}
|
||||
statuses = append(statuses, post)
|
||||
}
|
||||
if len(statuses) > perPage {
|
||||
return statuses[0:perPage], true, err
|
||||
}
|
||||
return statuses, false, err
|
||||
}
|
||||
|
||||
func (s *Storage) UpdateStatus(status model.Status) error {
|
||||
stmt, err := s.db.Prepare(`UPDATE statuses SET content = $1 WHERE id = $2 and author = $3;`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = stmt.Exec(status.Content, status.Id, status.User)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteStatus(id int64, author string) error {
|
||||
stmt, err := s.db.Prepare(`DELETE from statuses WHERE id = $1 and author = $2;`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = stmt.Exec(id, author)
|
||||
return err
|
||||
}
|
||||
13
storage/storage.go
Normal file
13
storage/storage.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func New(db *sql.DB) *Storage {
|
||||
return &Storage{db: db}
|
||||
}
|
||||
69
storage/user.go
Normal file
69
storage/user.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"status/model"
|
||||
)
|
||||
|
||||
const queryFindName = `SELECT name, hash, created_at FROM users WHERE name=lower($1);`
|
||||
const queryFindDomain = `SELECT name, hash, created_at FROM users WHERE domain=$1;`
|
||||
|
||||
func (s *Storage) queryUser(q string, params ...interface{}) (user model.User, err error) {
|
||||
err = s.db.QueryRow(q, params...).Scan(&user.Name, &user.Hash, &user.CreatedAt)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Storage) UserExists(name string) bool {
|
||||
var rv bool
|
||||
s.db.QueryRow(`SELECT true FROM users WHERE name=lower($1)`, name).Scan(&rv)
|
||||
return rv
|
||||
}
|
||||
|
||||
func (s *Storage) UserByName(name string) (model.User, error) {
|
||||
return s.queryUser(queryFindName, name)
|
||||
}
|
||||
|
||||
func (s *Storage) CreateUser(user model.User) error {
|
||||
hash, err := user.HashPassword()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
insertUser := `INSERT INTO users (name, hash) VALUES (lower($1), $2)`
|
||||
statement, err := s.db.Prepare(insertUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = statement.Exec(user.Name, hash)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Storage) Users() ([]string, error) {
|
||||
rows, err := s.db.Query("select name from users")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var users []string
|
||||
for rows.Next() {
|
||||
var user string
|
||||
err := rows.Scan(&user)
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteUser(username string) error {
|
||||
stmt, err := s.db.Prepare(`DELETE from status WHERE author = $1;`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = stmt.Exec(username)
|
||||
stmt, err = s.db.Prepare(`DELETE from users WHERE name = $1;`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = stmt.Exec(username)
|
||||
return err
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue