feature: add web & migrate commands
This commit is contained in:
40
internal/commands/commands.go
Normal file
40
internal/commands/commands.go
Normal file
@ -0,0 +1,40 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const CitadelConfDirEnvName = "CITADEL_CONFIG_DIR"
|
||||
|
||||
const CitadelAppConf = "citadel"
|
||||
|
||||
const mainDescription = `citadel is an open soure project management
|
||||
system written in Golang & React.`
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
commit = "none"
|
||||
date = "unknown"
|
||||
)
|
||||
|
||||
var versionTemplate = fmt.Sprintf(`Version: %s
|
||||
Commit: %s
|
||||
Built: %s`, version, commit, date+"\n")
|
||||
|
||||
var commandError error
|
||||
var configDir string
|
||||
var verbose bool
|
||||
var noColor bool
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "citadel",
|
||||
Long: mainDescription,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.SetVersionTemplate(versionTemplate)
|
||||
rootCmd.AddCommand(newWebCmd(), newMigrateCmd())
|
||||
rootCmd.Execute()
|
||||
}
|
68
internal/commands/migrate.go
Normal file
68
internal/commands/migrate.go
Normal file
@ -0,0 +1,68 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jordanknott/project-citadel/api/internal/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type MigrateLog struct {
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func (l *MigrateLog) Printf(format string, v ...interface{}) {
|
||||
log.Printf("%s", v)
|
||||
}
|
||||
|
||||
// Verbose shows if verbose print enabled
|
||||
func (l *MigrateLog) Verbose() bool {
|
||||
return l.verbose
|
||||
}
|
||||
|
||||
func newMigrateCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Run the database schema migrations",
|
||||
Long: "Run the database schema migrations",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
appConfig, err := config.LoadConfig("conf/app.toml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
|
||||
appConfig.Database.User,
|
||||
appConfig.Database.Password,
|
||||
appConfig.Database.Host,
|
||||
appConfig.Database.Name,
|
||||
)
|
||||
db, err := sqlx.Connect("postgres", connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
"file://migrations",
|
||||
"postgres", driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger := &MigrateLog{}
|
||||
m.Log = logger
|
||||
err = m.Up()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
52
internal/commands/web.go
Normal file
52
internal/commands/web.go
Normal file
@ -0,0 +1,52 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jordanknott/project-citadel/api/internal/config"
|
||||
"github.com/jordanknott/project-citadel/api/internal/route"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func newWebCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "web",
|
||||
Short: "Run the web server",
|
||||
Long: "Run the web & api server",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
appConfig, err := config.LoadConfig("conf/app.toml")
|
||||
if err != nil {
|
||||
log.WithError(err).Error("loading config")
|
||||
}
|
||||
Formatter := new(log.TextFormatter)
|
||||
Formatter.TimestampFormat = "02-01-2006 15:04:05"
|
||||
Formatter.FullTimestamp = true
|
||||
log.SetFormatter(Formatter)
|
||||
log.SetLevel(log.InfoLevel)
|
||||
|
||||
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
|
||||
appConfig.Database.User,
|
||||
appConfig.Database.Password,
|
||||
appConfig.Database.Host,
|
||||
appConfig.Database.Name,
|
||||
)
|
||||
db, err := sqlx.Connect("postgres", connection)
|
||||
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
db.SetMaxOpenConns(25)
|
||||
db.SetMaxIdleConns(25)
|
||||
db.SetConnMaxLifetime(5 * time.Minute)
|
||||
|
||||
defer db.Close()
|
||||
log.WithFields(log.Fields{"url": appConfig.General.Host}).Info("starting server")
|
||||
r, _ := route.NewRouter(appConfig, db)
|
||||
http.ListenAndServe(appConfig.General.Host, r)
|
||||
},
|
||||
}
|
||||
}
|
58
internal/config/config.go
Normal file
58
internal/config/config.go
Normal file
@ -0,0 +1,58 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/toml"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
Host string
|
||||
Name string
|
||||
User string
|
||||
Password string
|
||||
}
|
||||
|
||||
type General struct {
|
||||
Host string
|
||||
}
|
||||
|
||||
type EmailNotifications struct {
|
||||
Enabled bool
|
||||
DisplayName string `toml:"display_name"`
|
||||
FromAddress string `toml:"from_address"`
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
StorageSystem string `toml:"local_storage"`
|
||||
UploadDirPath string `toml:"upload_dir_path"`
|
||||
}
|
||||
|
||||
type Smtp struct {
|
||||
Username string
|
||||
Password string
|
||||
Server string
|
||||
Port int
|
||||
ConnectionSecurity string `toml:"connection_security"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
General General
|
||||
Database Database
|
||||
EmailNotifications EmailNotifications `toml:"email_notifications"`
|
||||
Storage Storage
|
||||
Smtp Smtp
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (AppConfig, error) {
|
||||
dat, err := ioutil.ReadFile("conf/app.toml")
|
||||
if err != nil {
|
||||
return AppConfig{}, err
|
||||
}
|
||||
|
||||
var appConfig AppConfig
|
||||
_, err = toml.Decode(string(dat), &appConfig)
|
||||
if err != nil {
|
||||
return AppConfig{}, err
|
||||
}
|
||||
return appConfig, nil
|
||||
}
|
@ -83,6 +83,7 @@ type Querier interface {
|
||||
SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
|
||||
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
|
||||
SetTeamOwner(ctx context.Context, arg SetTeamOwnerParams) (Team, error)
|
||||
SetUserPassword(ctx context.Context, arg SetUserPasswordParams) (UserAccount, error)
|
||||
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
|
||||
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
|
||||
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
|
||||
|
@ -25,3 +25,6 @@ WHERE user_id = $1;
|
||||
|
||||
-- name: UpdateUserRole :one
|
||||
UPDATE user_account SET role_code = $2 WHERE user_id = $1 RETURNING *;
|
||||
|
||||
-- name: SetUserPassword :one
|
||||
UPDATE user_account SET password_hash = $2 WHERE user_id = $1 RETURNING *;
|
||||
|
@ -162,6 +162,33 @@ func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const setUserPassword = `-- name: SetUserPassword :one
|
||||
UPDATE user_account SET password_hash = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code
|
||||
`
|
||||
|
||||
type SetUserPasswordParams struct {
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
}
|
||||
|
||||
func (q *Queries) SetUserPassword(ctx context.Context, arg SetUserPasswordParams) (UserAccount, error) {
|
||||
row := q.db.QueryRowContext(ctx, setUserPassword, arg.UserID, arg.PasswordHash)
|
||||
var i UserAccount
|
||||
err := row.Scan(
|
||||
&i.UserID,
|
||||
&i.CreatedAt,
|
||||
&i.Email,
|
||||
&i.Username,
|
||||
&i.PasswordHash,
|
||||
&i.ProfileBgColor,
|
||||
&i.FullName,
|
||||
&i.Initials,
|
||||
&i.ProfileAvatarUrl,
|
||||
&i.RoleCode,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateUserAccountProfileAvatarURL = `-- name: UpdateUserAccountProfileAvatarURL :one
|
||||
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
|
||||
RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code
|
||||
|
@ -185,6 +185,7 @@ type ComplexityRoot struct {
|
||||
UpdateTaskLocation func(childComplexity int, input NewTaskLocation) int
|
||||
UpdateTaskName func(childComplexity int, input UpdateTaskName) int
|
||||
UpdateTeamMemberRole func(childComplexity int, input UpdateTeamMemberRole) int
|
||||
UpdateUserPassword func(childComplexity int, input UpdateUserPassword) int
|
||||
UpdateUserRole func(childComplexity int, input UpdateUserRole) int
|
||||
}
|
||||
|
||||
@ -347,6 +348,11 @@ type ComplexityRoot struct {
|
||||
Ok func(childComplexity int) int
|
||||
}
|
||||
|
||||
UpdateUserPasswordPayload struct {
|
||||
Ok func(childComplexity int) int
|
||||
User func(childComplexity int) int
|
||||
}
|
||||
|
||||
UpdateUserRolePayload struct {
|
||||
User func(childComplexity int) int
|
||||
}
|
||||
@ -415,6 +421,7 @@ type MutationResolver interface {
|
||||
DeleteUserAccount(ctx context.Context, input DeleteUserAccount) (*DeleteUserAccountPayload, error)
|
||||
LogoutUser(ctx context.Context, input LogoutUser) (bool, error)
|
||||
ClearProfileAvatar(ctx context.Context) (*db.UserAccount, error)
|
||||
UpdateUserPassword(ctx context.Context, input UpdateUserPassword) (*UpdateUserPasswordPayload, error)
|
||||
UpdateUserRole(ctx context.Context, input UpdateUserRole) (*UpdateUserRolePayload, error)
|
||||
}
|
||||
type OrganizationResolver interface {
|
||||
@ -1341,6 +1348,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Mutation.UpdateTeamMemberRole(childComplexity, args["input"].(UpdateTeamMemberRole)), true
|
||||
|
||||
case "Mutation.updateUserPassword":
|
||||
if e.complexity.Mutation.UpdateUserPassword == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_updateUserPassword_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.UpdateUserPassword(childComplexity, args["input"].(UpdateUserPassword)), true
|
||||
|
||||
case "Mutation.updateUserRole":
|
||||
if e.complexity.Mutation.UpdateUserRole == nil {
|
||||
break
|
||||
@ -2008,6 +2027,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.UpdateTeamMemberRolePayload.Ok(childComplexity), true
|
||||
|
||||
case "UpdateUserPasswordPayload.ok":
|
||||
if e.complexity.UpdateUserPasswordPayload.Ok == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.UpdateUserPasswordPayload.Ok(childComplexity), true
|
||||
|
||||
case "UpdateUserPasswordPayload.user":
|
||||
if e.complexity.UpdateUserPasswordPayload.User == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.UpdateUserPasswordPayload.User(childComplexity), true
|
||||
|
||||
case "UpdateUserRolePayload.user":
|
||||
if e.complexity.UpdateUserRolePayload.User == nil {
|
||||
break
|
||||
@ -2707,9 +2740,20 @@ extend type Mutation {
|
||||
logoutUser(input: LogoutUser!): Boolean!
|
||||
clearProfileAvatar: UserAccount!
|
||||
|
||||
updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
|
||||
updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload!
|
||||
}
|
||||
|
||||
input UpdateUserPassword {
|
||||
userID: UUID!
|
||||
password: String!
|
||||
}
|
||||
|
||||
type UpdateUserPasswordPayload {
|
||||
ok: Boolean!
|
||||
user: UserAccount!
|
||||
}
|
||||
|
||||
input UpdateUserRole {
|
||||
userID: UUID!
|
||||
roleCode: RoleCode!
|
||||
@ -3411,6 +3455,20 @@ func (ec *executionContext) field_Mutation_updateTeamMemberRole_args(ctx context
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_updateUserPassword_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
var arg0 UpdateUserPassword
|
||||
if tmp, ok := rawArgs["input"]; ok {
|
||||
arg0, err = ec.unmarshalNUpdateUserPassword2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPassword(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_updateUserRole_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
@ -6761,6 +6819,47 @@ func (ec *executionContext) _Mutation_clearProfileAvatar(ctx context.Context, fi
|
||||
return ec.marshalNUserAccount2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋdbᚐUserAccount(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_updateUserPassword(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Mutation",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: true,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
rawArgs := field.ArgumentMap(ec.Variables)
|
||||
args, err := ec.field_Mutation_updateUserPassword_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
fc.Args = args
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Mutation().UpdateUserPassword(rctx, args["input"].(UpdateUserPassword))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*UpdateUserPasswordPayload)
|
||||
fc.Result = res
|
||||
return ec.marshalNUpdateUserPasswordPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPasswordPayload(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_updateUserRole(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@ -9945,6 +10044,74 @@ func (ec *executionContext) _UpdateTeamMemberRolePayload_member(ctx context.Cont
|
||||
return ec.marshalNMember2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐMember(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _UpdateUserPasswordPayload_ok(ctx context.Context, field graphql.CollectedField, obj *UpdateUserPasswordPayload) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "UpdateUserPasswordPayload",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Ok, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(bool)
|
||||
fc.Result = res
|
||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _UpdateUserPasswordPayload_user(ctx context.Context, field graphql.CollectedField, obj *UpdateUserPasswordPayload) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "UpdateUserPasswordPayload",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.User, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*db.UserAccount)
|
||||
fc.Result = res
|
||||
return ec.marshalNUserAccount2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋdbᚐUserAccount(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _UpdateUserRolePayload_user(ctx context.Context, field graphql.CollectedField, obj *UpdateUserRolePayload) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@ -12554,6 +12721,30 @@ func (ec *executionContext) unmarshalInputUpdateTeamMemberRole(ctx context.Conte
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputUpdateUserPassword(ctx context.Context, obj interface{}) (UpdateUserPassword, error) {
|
||||
var it UpdateUserPassword
|
||||
var asMap = obj.(map[string]interface{})
|
||||
|
||||
for k, v := range asMap {
|
||||
switch k {
|
||||
case "userID":
|
||||
var err error
|
||||
it.UserID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "password":
|
||||
var err error
|
||||
it.Password, err = ec.unmarshalNString2string(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputUpdateUserRole(ctx context.Context, obj interface{}) (UpdateUserRole, error) {
|
||||
var it UpdateUserRole
|
||||
var asMap = obj.(map[string]interface{})
|
||||
@ -13340,6 +13531,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "updateUserPassword":
|
||||
out.Values[i] = ec._Mutation_updateUserPassword(ctx, field)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "updateUserRole":
|
||||
out.Values[i] = ec._Mutation_updateUserRole(ctx, field)
|
||||
if out.Values[i] == graphql.Null {
|
||||
@ -14668,6 +14864,38 @@ func (ec *executionContext) _UpdateTeamMemberRolePayload(ctx context.Context, se
|
||||
return out
|
||||
}
|
||||
|
||||
var updateUserPasswordPayloadImplementors = []string{"UpdateUserPasswordPayload"}
|
||||
|
||||
func (ec *executionContext) _UpdateUserPasswordPayload(ctx context.Context, sel ast.SelectionSet, obj *UpdateUserPasswordPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, updateUserPasswordPayloadImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
var invalids uint32
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("UpdateUserPasswordPayload")
|
||||
case "ok":
|
||||
out.Values[i] = ec._UpdateUserPasswordPayload_ok(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "user":
|
||||
out.Values[i] = ec._UpdateUserPasswordPayload_user(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch()
|
||||
if invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var updateUserRolePayloadImplementors = []string{"UpdateUserRolePayload"}
|
||||
|
||||
func (ec *executionContext) _UpdateUserRolePayload(ctx context.Context, sel ast.SelectionSet, obj *UpdateUserRolePayload) graphql.Marshaler {
|
||||
@ -16230,6 +16458,24 @@ func (ec *executionContext) marshalNUpdateTeamMemberRolePayload2ᚖgithubᚗcom
|
||||
return ec._UpdateTeamMemberRolePayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNUpdateUserPassword2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPassword(ctx context.Context, v interface{}) (UpdateUserPassword, error) {
|
||||
return ec.unmarshalInputUpdateUserPassword(ctx, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNUpdateUserPasswordPayload2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPasswordPayload(ctx context.Context, sel ast.SelectionSet, v UpdateUserPasswordPayload) graphql.Marshaler {
|
||||
return ec._UpdateUserPasswordPayload(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNUpdateUserPasswordPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPasswordPayload(ctx context.Context, sel ast.SelectionSet, v *UpdateUserPasswordPayload) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._UpdateUserPasswordPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNUpdateUserRole2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserRole(ctx context.Context, v interface{}) (UpdateUserRole, error) {
|
||||
return ec.unmarshalInputUpdateUserRole(ctx, v)
|
||||
}
|
||||
|
@ -12,13 +12,15 @@ import (
|
||||
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||
"github.com/99designs/gqlgen/graphql/playground"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jordanknott/project-citadel/api/internal/config"
|
||||
"github.com/jordanknott/project-citadel/api/internal/db"
|
||||
)
|
||||
|
||||
// NewHandler returns a new graphql endpoint handler.
|
||||
func NewHandler(repo db.Repository) http.Handler {
|
||||
func NewHandler(config config.AppConfig, repo db.Repository) http.Handler {
|
||||
srv := handler.New(NewExecutableSchema(Config{
|
||||
Resolvers: &Resolver{
|
||||
Config: config,
|
||||
Repository: repo,
|
||||
},
|
||||
}))
|
||||
|
@ -401,6 +401,16 @@ type UpdateTeamMemberRolePayload struct {
|
||||
Member *Member `json:"member"`
|
||||
}
|
||||
|
||||
type UpdateUserPassword struct {
|
||||
UserID uuid.UUID `json:"userID"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type UpdateUserPasswordPayload struct {
|
||||
Ok bool `json:"ok"`
|
||||
User *db.UserAccount `json:"user"`
|
||||
}
|
||||
|
||||
type UpdateUserRole struct {
|
||||
UserID uuid.UUID `json:"userID"`
|
||||
RoleCode RoleCode `json:"roleCode"`
|
||||
|
@ -5,10 +5,12 @@ package graph
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jordanknott/project-citadel/api/internal/config"
|
||||
"github.com/jordanknott/project-citadel/api/internal/db"
|
||||
)
|
||||
|
||||
type Resolver struct {
|
||||
Config config.AppConfig
|
||||
Repository db.Repository
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
@ -570,9 +570,20 @@ extend type Mutation {
|
||||
logoutUser(input: LogoutUser!): Boolean!
|
||||
clearProfileAvatar: UserAccount!
|
||||
|
||||
updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
|
||||
updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload!
|
||||
}
|
||||
|
||||
input UpdateUserPassword {
|
||||
userID: UUID!
|
||||
password: String!
|
||||
}
|
||||
|
||||
type UpdateUserPasswordPayload {
|
||||
ok: Boolean!
|
||||
user: UserAccount!
|
||||
}
|
||||
|
||||
input UpdateUserRole {
|
||||
userID: UUID!
|
||||
roleCode: RoleCode!
|
||||
|
@ -783,13 +783,24 @@ func (r *mutationResolver) ClearProfileAvatar(ctx context.Context) (*db.UserAcco
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UpdateUserPassword(ctx context.Context, input UpdateUserPassword) (*UpdateUserPasswordPayload, error) {
|
||||
hashedPwd, err := bcrypt.GenerateFromPassword([]byte(input.Password), 14)
|
||||
if err != nil {
|
||||
return &UpdateUserPasswordPayload{}, err
|
||||
}
|
||||
user, err := r.Repository.SetUserPassword(ctx, db.SetUserPasswordParams{UserID: input.UserID, PasswordHash: string(hashedPwd)})
|
||||
if err != nil {
|
||||
return &UpdateUserPasswordPayload{}, err
|
||||
}
|
||||
return &UpdateUserPasswordPayload{Ok: true, User: &user}, err
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UpdateUserRole(ctx context.Context, input UpdateUserRole) (*UpdateUserRolePayload, error) {
|
||||
user, err := r.Repository.UpdateUserRole(ctx, db.UpdateUserRoleParams{RoleCode: input.RoleCode.String(), UserID: input.UserID})
|
||||
if err != nil {
|
||||
return &UpdateUserRolePayload{}, err
|
||||
}
|
||||
return &UpdateUserRolePayload{User: &user}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *organizationResolver) ID(ctx context.Context, obj *db.Organization) (uuid.UUID, error) {
|
||||
|
@ -5,9 +5,20 @@ extend type Mutation {
|
||||
logoutUser(input: LogoutUser!): Boolean!
|
||||
clearProfileAvatar: UserAccount!
|
||||
|
||||
updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
|
||||
updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload!
|
||||
}
|
||||
|
||||
input UpdateUserPassword {
|
||||
userID: UUID!
|
||||
password: String!
|
||||
}
|
||||
|
||||
type UpdateUserPasswordPayload {
|
||||
ok: Boolean!
|
||||
user: UserAccount!
|
||||
}
|
||||
|
||||
input UpdateUserRole {
|
||||
userID: UUID!
|
||||
roleCode: RoleCode!
|
||||
|
@ -5,13 +5,27 @@ import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/jordanknott/project-citadel/api/internal/db"
|
||||
"github.com/jordanknott/project-citadel/api/internal/frontend"
|
||||
)
|
||||
|
||||
func (h *CitadelHandler) Frontend(w http.ResponseWriter, r *http.Request) {
|
||||
f, err := frontend.Frontend.Open("index.h")
|
||||
if os.IsNotExist(err) {
|
||||
log.Warning("does not exist")
|
||||
} else if err != nil {
|
||||
log.WithError(err).Error("frontend")
|
||||
}
|
||||
http.ServeContent(w, r, "index.html", time.Now(), f)
|
||||
}
|
||||
|
||||
func (h *CitadelHandler) ProfileImageUpload(w http.ResponseWriter, r *http.Request) {
|
||||
log.Info("preparing to upload file")
|
||||
userID, ok := r.Context().Value("userID").(uuid.UUID)
|
||||
|
@ -10,16 +10,61 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/jordanknott/project-citadel/api/internal/config"
|
||||
"github.com/jordanknott/project-citadel/api/internal/db"
|
||||
"github.com/jordanknott/project-citadel/api/internal/frontend"
|
||||
"github.com/jordanknott/project-citadel/api/internal/graph"
|
||||
"github.com/jordanknott/project-citadel/api/internal/logger"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type CitadelHandler struct {
|
||||
repo db.Repository
|
||||
// spaHandler implements the http.Handler interface, so we can use it
|
||||
// to respond to HTTP requests. The path to the static directory and
|
||||
// path to the index file within that static directory are used to
|
||||
// serve the SPA in the given static directory.
|
||||
type FrontendHandler struct {
|
||||
staticPath string
|
||||
indexPath string
|
||||
}
|
||||
|
||||
func NewRouter(dbConnection *sqlx.DB) (chi.Router, error) {
|
||||
func IsDir(f http.File) bool {
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return fi.IsDir()
|
||||
}
|
||||
|
||||
func (h FrontendHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path, err := filepath.Abs(r.URL.Path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
f, err := frontend.Frontend.Open(path)
|
||||
if os.IsNotExist(err) || IsDir(f) {
|
||||
index, err := frontend.Frontend.Open("index.html")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.ServeContent(w, r, "index.html", time.Now(), index)
|
||||
return
|
||||
} else if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.ServeContent(w, r, path, time.Now(), f)
|
||||
}
|
||||
|
||||
type CitadelHandler struct {
|
||||
config config.AppConfig
|
||||
repo db.Repository
|
||||
}
|
||||
|
||||
func NewRouter(config config.AppConfig, dbConnection *sqlx.DB) (chi.Router, error) {
|
||||
formatter := new(log.TextFormatter)
|
||||
formatter.TimestampFormat = "02-01-2006 15:04:05"
|
||||
formatter.FullTimestamp = true
|
||||
@ -47,7 +92,7 @@ func NewRouter(dbConnection *sqlx.DB) (chi.Router, error) {
|
||||
r.Use(middleware.Timeout(60 * time.Second))
|
||||
|
||||
repository := db.NewRepository(dbConnection)
|
||||
citadelHandler := CitadelHandler{*repository}
|
||||
citadelHandler := CitadelHandler{config, *repository}
|
||||
|
||||
var imgServer = http.FileServer(http.Dir("./uploads/"))
|
||||
r.Group(func(mux chi.Router) {
|
||||
@ -59,8 +104,11 @@ func NewRouter(dbConnection *sqlx.DB) (chi.Router, error) {
|
||||
r.Group(func(mux chi.Router) {
|
||||
mux.Use(AuthenticationMiddleware)
|
||||
mux.Post("/users/me/avatar", citadelHandler.ProfileImageUpload)
|
||||
mux.Handle("/graphql", graph.NewHandler(*repository))
|
||||
mux.Handle("/graphql", graph.NewHandler(config, *repository))
|
||||
})
|
||||
|
||||
frontend := FrontendHandler{staticPath: "build", indexPath: "index.html"}
|
||||
r.Handle("/*", frontend)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user