Skip to content

Commit 93e183b

Browse files
sosyzshuashuai
authored andcommitted
feat: Add resetPassword cli tool
1 parent b33e9fc commit 93e183b

File tree

12 files changed

+417
-64
lines changed

12 files changed

+417
-64
lines changed

cmd/command.go

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
package answercmd
2121

2222
import (
23+
"context"
2324
"fmt"
2425
"os"
2526
"strings"
2627

2728
"github.com/apache/answer/internal/base/conf"
29+
"github.com/apache/answer/internal/base/path"
2830
"github.com/apache/answer/internal/cli"
2931
"github.com/apache/answer/internal/install"
3032
"github.com/apache/answer/internal/migrations"
@@ -53,6 +55,10 @@ var (
5355
i18nSourcePath string
5456
// i18nTargetPath i18n to path
5557
i18nTargetPath string
58+
// resetPasswordEmail user email for password reset
59+
resetPasswordEmail string
60+
// resetPasswordPassword new password for password reset
61+
resetPasswordPassword string
5662
)
5763

5864
func init() {
@@ -76,7 +82,10 @@ func init() {
7682

7783
i18nCmd.Flags().StringVarP(&i18nTargetPath, "target", "t", "", "i18n target path, eg: -t ./i18n/target")
7884

79-
for _, cmd := range []*cobra.Command{initCmd, checkCmd, runCmd, dumpCmd, upgradeCmd, buildCmd, pluginCmd, configCmd, i18nCmd} {
85+
resetPasswordCmd.Flags().StringVarP(&resetPasswordEmail, "email", "e", "", "user email address")
86+
resetPasswordCmd.Flags().StringVarP(&resetPasswordPassword, "password", "p", "", "new password (not recommended, will be recorded in shell history)")
87+
88+
for _, cmd := range []*cobra.Command{initCmd, checkCmd, runCmd, dumpCmd, upgradeCmd, buildCmd, pluginCmd, configCmd, i18nCmd, resetPasswordCmd} {
8089
rootCmd.AddCommand(cmd)
8190
}
8291
}
@@ -96,8 +105,8 @@ To run answer, use:
96105
Short: "Run Answer",
97106
Long: `Start running Answer`,
98107
Run: func(_ *cobra.Command, _ []string) {
99-
cli.FormatAllPath(dataDirPath)
100-
fmt.Println("config file path: ", cli.GetConfigFilePath())
108+
path.FormatAllPath(dataDirPath)
109+
fmt.Println("config file path: ", path.GetConfigFilePath())
101110
fmt.Println("Answer is starting..........................")
102111
runApp()
103112
},
@@ -111,10 +120,10 @@ To run answer, use:
111120
// check config file and database. if config file exists and database is already created, init done
112121
cli.InstallAllInitialEnvironment(dataDirPath)
113122

114-
configFileExist := cli.CheckConfigFile(cli.GetConfigFilePath())
123+
configFileExist := cli.CheckConfigFile(path.GetConfigFilePath())
115124
if configFileExist {
116125
fmt.Println("config file exists, try to read the config...")
117-
c, err := conf.ReadConfig(cli.GetConfigFilePath())
126+
c, err := conf.ReadConfig(path.GetConfigFilePath())
118127
if err != nil {
119128
fmt.Println("read config failed: ", err.Error())
120129
return
@@ -128,7 +137,7 @@ To run answer, use:
128137
}
129138

130139
// start installation server to install
131-
install.Run(cli.GetConfigFilePath())
140+
install.Run(path.GetConfigFilePath())
132141
},
133142
}
134143

@@ -138,9 +147,9 @@ To run answer, use:
138147
Long: `Upgrade Answer to the latest version`,
139148
Run: func(_ *cobra.Command, _ []string) {
140149
log.SetLogger(log.NewStdLogger(os.Stdout))
141-
cli.FormatAllPath(dataDirPath)
150+
path.FormatAllPath(dataDirPath)
142151
cli.InstallI18nBundle(true)
143-
c, err := conf.ReadConfig(cli.GetConfigFilePath())
152+
c, err := conf.ReadConfig(path.GetConfigFilePath())
144153
if err != nil {
145154
fmt.Println("read config failed: ", err.Error())
146155
return
@@ -159,8 +168,8 @@ To run answer, use:
159168
Long: `Back up database into an SQL file`,
160169
Run: func(_ *cobra.Command, _ []string) {
161170
fmt.Println("Answer is backing up data")
162-
cli.FormatAllPath(dataDirPath)
163-
c, err := conf.ReadConfig(cli.GetConfigFilePath())
171+
path.FormatAllPath(dataDirPath)
172+
c, err := conf.ReadConfig(path.GetConfigFilePath())
164173
if err != nil {
165174
fmt.Println("read config failed: ", err.Error())
166175
return
@@ -179,9 +188,9 @@ To run answer, use:
179188
Short: "Check the required environment",
180189
Long: `Check if the current environment meets the startup requirements`,
181190
Run: func(_ *cobra.Command, _ []string) {
182-
cli.FormatAllPath(dataDirPath)
191+
path.FormatAllPath(dataDirPath)
183192
fmt.Println("Start checking the required environment...")
184-
if cli.CheckConfigFile(cli.GetConfigFilePath()) {
193+
if cli.CheckConfigFile(path.GetConfigFilePath()) {
185194
fmt.Println("config file exists [✔]")
186195
} else {
187196
fmt.Println("config file not exists [x]")
@@ -193,7 +202,7 @@ To run answer, use:
193202
fmt.Println("upload directory not exists [x]")
194203
}
195204

196-
c, err := conf.ReadConfig(cli.GetConfigFilePath())
205+
c, err := conf.ReadConfig(path.GetConfigFilePath())
197206
if err != nil {
198207
fmt.Println("read config failed: ", err.Error())
199208
return
@@ -246,9 +255,9 @@ To run answer, use:
246255
Short: "Set some config to default value",
247256
Long: `Set some config to default value`,
248257
Run: func(_ *cobra.Command, _ []string) {
249-
cli.FormatAllPath(dataDirPath)
258+
path.FormatAllPath(dataDirPath)
250259

251-
c, err := conf.ReadConfig(cli.GetConfigFilePath())
260+
c, err := conf.ReadConfig(path.GetConfigFilePath())
252261
if err != nil {
253262
fmt.Println("read config failed: ", err.Error())
254263
return
@@ -297,6 +306,32 @@ To run answer, use:
297306
}
298307
},
299308
}
309+
310+
resetPasswordCmd = &cobra.Command{
311+
Use: "passwd",
312+
Aliases: []string{"password", "reset-password"},
313+
Short: "Reset user password",
314+
Long: "Reset user password by email address.",
315+
Example: ` # Interactive mode (recommended, safest)
316+
answer passwd -C ./answer-data
317+
318+
# Specify email only (will prompt for password securely)
319+
answer passwd -C ./answer-data --email user@example.com
320+
answer passwd -C ./answer-data -e user@example.com
321+
322+
# Specify email and password (NOT recommended, will be recorded in shell history)
323+
answer passwd -C ./answer-data -e user@example.com -p newpassword123`,
324+
Run: func(cmd *cobra.Command, args []string) {
325+
opts := &cli.ResetPasswordOptions{
326+
Email: resetPasswordEmail,
327+
Password: resetPasswordPassword,
328+
}
329+
if err := cli.ResetPassword(context.Background(), dataDirPath, opts); err != nil {
330+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
331+
os.Exit(1)
332+
}
333+
},
334+
}
300335
)
301336

302337
// Execute adds all child commands to the root command and sets flags appropriately.

cmd/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"github.com/apache/answer/internal/base/conf"
2929
"github.com/apache/answer/internal/base/constant"
3030
"github.com/apache/answer/internal/base/cron"
31-
"github.com/apache/answer/internal/cli"
31+
"github.com/apache/answer/internal/base/path"
3232
"github.com/apache/answer/internal/schema"
3333
"github.com/gin-gonic/gin"
3434
"github.com/segmentfault/pacman"
@@ -67,7 +67,7 @@ func Main() {
6767
}
6868

6969
func runApp() {
70-
c, err := conf.ReadConfig(cli.GetConfigFilePath())
70+
c, err := conf.ReadConfig(path.GetConfigFilePath())
7171
if err != nil {
7272
panic(err)
7373
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ require (
6060
golang.org/x/crypto v0.36.0
6161
golang.org/x/image v0.20.0
6262
golang.org/x/net v0.38.0
63+
golang.org/x/term v0.30.0
6364
golang.org/x/text v0.23.0
6465
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
6566
gopkg.in/yaml.v3 v3.0.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,8 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
785785
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
786786
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
787787
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
788+
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
789+
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
788790
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
789791
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
790792
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

internal/base/conf/conf.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ import (
2525
"path/filepath"
2626

2727
"github.com/apache/answer/internal/base/data"
28+
"github.com/apache/answer/internal/base/path"
2829
"github.com/apache/answer/internal/base/server"
2930
"github.com/apache/answer/internal/base/translator"
30-
"github.com/apache/answer/internal/cli"
3131
"github.com/apache/answer/internal/router"
3232
"github.com/apache/answer/internal/service/service_config"
3333
"github.com/apache/answer/pkg/writer"
@@ -98,7 +98,7 @@ func (c *AllConfig) SetEnvironmentOverrides() {
9898
// ReadConfig read config
9999
func ReadConfig(configFilePath string) (c *AllConfig, err error) {
100100
if len(configFilePath) == 0 {
101-
configFilePath = filepath.Join(cli.ConfigFileDir, cli.DefaultConfigFileName)
101+
configFilePath = filepath.Join(path.ConfigFileDir, path.DefaultConfigFileName)
102102
}
103103
c = &AllConfig{}
104104
config, err := viper.NewWithPath(configFilePath)

internal/base/path/path.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package path
21+
22+
import (
23+
"path/filepath"
24+
"sync"
25+
)
26+
27+
const (
28+
DefaultConfigFileName = "config.yaml"
29+
DefaultCacheFileName = "cache.db"
30+
DefaultReservedUsernamesConfigFileName = "reserved-usernames.json"
31+
)
32+
33+
var (
34+
ConfigFileDir = "/conf/"
35+
UploadFilePath = "/uploads/"
36+
I18nPath = "/i18n/"
37+
CacheDir = "/cache/"
38+
formatAllPathOnce sync.Once
39+
)
40+
41+
func FormatAllPath(dataDirPath string) {
42+
formatAllPathOnce.Do(func() {
43+
ConfigFileDir = filepath.Join(dataDirPath, ConfigFileDir)
44+
UploadFilePath = filepath.Join(dataDirPath, UploadFilePath)
45+
I18nPath = filepath.Join(dataDirPath, I18nPath)
46+
CacheDir = filepath.Join(dataDirPath, CacheDir)
47+
})
48+
}
49+
50+
// GetConfigFilePath get config file path
51+
func GetConfigFilePath() string {
52+
return filepath.Join(ConfigFileDir, DefaultConfigFileName)
53+
}

internal/cli/install.go

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,53 +23,25 @@ import (
2323
"fmt"
2424
"os"
2525
"path/filepath"
26-
"sync"
2726

2827
"github.com/apache/answer/configs"
2928
"github.com/apache/answer/i18n"
29+
"github.com/apache/answer/internal/base/path"
3030
"github.com/apache/answer/pkg/dir"
3131
"github.com/apache/answer/pkg/writer"
3232
)
3333

34-
const (
35-
DefaultConfigFileName = "config.yaml"
36-
DefaultCacheFileName = "cache.db"
37-
DefaultReservedUsernamesConfigFileName = "reserved-usernames.json"
38-
)
39-
40-
var (
41-
ConfigFileDir = "/conf/"
42-
UploadFilePath = "/uploads/"
43-
I18nPath = "/i18n/"
44-
CacheDir = "/cache/"
45-
formatAllPathONCE sync.Once
46-
)
47-
48-
// GetConfigFilePath get config file path
49-
func GetConfigFilePath() string {
50-
return filepath.Join(ConfigFileDir, DefaultConfigFileName)
51-
}
52-
53-
func FormatAllPath(dataDirPath string) {
54-
formatAllPathONCE.Do(func() {
55-
ConfigFileDir = filepath.Join(dataDirPath, ConfigFileDir)
56-
UploadFilePath = filepath.Join(dataDirPath, UploadFilePath)
57-
I18nPath = filepath.Join(dataDirPath, I18nPath)
58-
CacheDir = filepath.Join(dataDirPath, CacheDir)
59-
})
60-
}
61-
6234
// InstallAllInitialEnvironment install all initial environment
6335
func InstallAllInitialEnvironment(dataDirPath string) {
64-
FormatAllPath(dataDirPath)
36+
path.FormatAllPath(dataDirPath)
6537
installUploadDir()
6638
InstallI18nBundle(false)
6739
fmt.Println("install all initial environment done")
6840
}
6941

7042
func InstallConfigFile(configFilePath string) error {
7143
if len(configFilePath) == 0 {
72-
configFilePath = filepath.Join(ConfigFileDir, DefaultConfigFileName)
44+
configFilePath = filepath.Join(path.ConfigFileDir, path.DefaultConfigFileName)
7345
}
7446
fmt.Println("[config-file] try to create at ", configFilePath)
7547

@@ -79,7 +51,7 @@ func InstallConfigFile(configFilePath string) error {
7951
return nil
8052
}
8153

82-
if err := dir.CreateDirIfNotExist(ConfigFileDir); err != nil {
54+
if err := dir.CreateDirIfNotExist(path.ConfigFileDir); err != nil {
8355
fmt.Printf("[config-file] create directory fail %s\n", err.Error())
8456
return fmt.Errorf("create directory fail %s", err.Error())
8557
}
@@ -95,10 +67,10 @@ func InstallConfigFile(configFilePath string) error {
9567

9668
func installUploadDir() {
9769
fmt.Println("[upload-dir] try to install...")
98-
if err := dir.CreateDirIfNotExist(UploadFilePath); err != nil {
70+
if err := dir.CreateDirIfNotExist(path.UploadFilePath); err != nil {
9971
fmt.Printf("[upload-dir] install fail %s\n", err.Error())
10072
} else {
101-
fmt.Printf("[upload-dir] install success, upload directory is %s\n", UploadFilePath)
73+
fmt.Printf("[upload-dir] install success, upload directory is %s\n", path.UploadFilePath)
10274
}
10375
}
10476

@@ -108,7 +80,7 @@ func InstallI18nBundle(replace bool) {
10880
if len(os.Getenv("SKIP_REPLACE_I18N")) > 0 {
10981
replace = false
11082
}
111-
if err := dir.CreateDirIfNotExist(I18nPath); err != nil {
83+
if err := dir.CreateDirIfNotExist(path.I18nPath); err != nil {
11284
fmt.Println(err.Error())
11385
return
11486
}
@@ -120,7 +92,7 @@ func InstallI18nBundle(replace bool) {
12092
}
12193
fmt.Printf("[i18n] find i18n bundle %d\n", len(i18nList))
12294
for _, item := range i18nList {
123-
path := filepath.Join(I18nPath, item.Name())
95+
path := filepath.Join(path.I18nPath, item.Name())
12496
content, err := i18n.I18n.ReadFile(item.Name())
12597
if err != nil {
12698
continue

internal/cli/install_check.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424

2525
"github.com/apache/answer/internal/base/data"
26+
"github.com/apache/answer/internal/base/path"
2627
"github.com/apache/answer/internal/entity"
2728
"github.com/apache/answer/pkg/dir"
2829
)
@@ -32,7 +33,7 @@ func CheckConfigFile(configPath string) bool {
3233
}
3334

3435
func CheckUploadDir() bool {
35-
return dir.CheckDirExist(UploadFilePath)
36+
return dir.CheckDirExist(path.UploadFilePath)
3637
}
3738

3839
// CheckDBConnection check database whether the connection is normal

0 commit comments

Comments
 (0)