chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package install
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/apache/answer/configs"
|
||||
"github.com/apache/answer/internal/base/conf"
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/base/handler"
|
||||
"github.com/apache/answer/internal/base/path"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/base/translator"
|
||||
"github.com/apache/answer/internal/cli"
|
||||
"github.com/apache/answer/internal/migrations"
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// LangOptions get installation language options
|
||||
// @Summary get installation language options
|
||||
// @Description get installation language options
|
||||
// @Tags Lang
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{data=[]translator.LangOption}
|
||||
// @Router /installation/language/options [get]
|
||||
func LangOptions(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, nil, translator.LanguageOptions)
|
||||
}
|
||||
|
||||
// GetLangMapping get installation language config mapping
|
||||
// @Summary get installation language config mapping
|
||||
// @Description get installation language config mapping
|
||||
// @Tags Lang
|
||||
// @Param lang query string true "installation language"
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{}
|
||||
// @Router /installation/language/config [get]
|
||||
func GetLangMapping(ctx *gin.Context) {
|
||||
t, err := translator.NewTranslator(&translator.I18n{BundleDir: path.I18nPath})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
lang := ctx.Query("lang")
|
||||
trData, _ := t.Dump(i18n.Language(lang))
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(trData, &resp)
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
}
|
||||
|
||||
// CheckConfigFileAndRedirectToInstallPage if config file not exist try to redirect to install page
|
||||
// @Summary if config file not exist try to redirect to install page
|
||||
// @Description if config file not exist try to redirect to install page
|
||||
// @Tags installation
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Router / [get]
|
||||
func CheckConfigFileAndRedirectToInstallPage(ctx *gin.Context) {
|
||||
if cli.CheckConfigFile(confPath) {
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
} else {
|
||||
ctx.Redirect(http.StatusFound, "/install")
|
||||
}
|
||||
}
|
||||
|
||||
// CheckConfigFile check config file if exist when installation
|
||||
// @Summary check config file if exist when installation
|
||||
// @Description check config file if exist when installation
|
||||
// @Tags installation
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{data=install.CheckConfigFileResp{}}
|
||||
// @Router /installation/config-file/check [post]
|
||||
func CheckConfigFile(ctx *gin.Context) {
|
||||
resp := &CheckConfigFileResp{}
|
||||
resp.ConfigFileExist = cli.CheckConfigFile(confPath)
|
||||
if !resp.ConfigFileExist {
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
return
|
||||
}
|
||||
allConfig, err := conf.ReadConfig(confPath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
err = errors.BadRequest(reason.ReadConfigFailed)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
resp.DBConnectionSuccess = cli.CheckDBConnection(allConfig.Data.Database)
|
||||
if resp.DBConnectionSuccess {
|
||||
resp.DbTableExist = cli.CheckDBTableExist(allConfig.Data.Database)
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
}
|
||||
|
||||
// CheckDatabase check database if exist when installation
|
||||
// @Summary check database if exist when installation
|
||||
// @Description check database if exist when installation
|
||||
// @Tags installation
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body install.CheckDatabaseReq true "CheckDatabaseReq"
|
||||
// @Success 200 {object} handler.RespBody{data=install.CheckConfigFileResp{}}
|
||||
// @Router /installation/db/check [post]
|
||||
func CheckDatabase(ctx *gin.Context) {
|
||||
req := &CheckDatabaseReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
resp := &CheckDatabaseResp{}
|
||||
dataConf := &data.Database{
|
||||
Driver: req.DbType,
|
||||
Connection: req.GetConnection(),
|
||||
}
|
||||
resp.ConnectionSuccess = cli.CheckDBConnection(dataConf)
|
||||
if !resp.ConnectionSuccess {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.DatabaseConnectionFailed), schema.ErrTypeAlert)
|
||||
return
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
}
|
||||
|
||||
// InitEnvironment init environment
|
||||
// @Summary init environment
|
||||
// @Description init environment
|
||||
// @Tags installation
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body install.CheckDatabaseReq true "CheckDatabaseReq"
|
||||
// @Success 200 {object} handler.RespBody{}
|
||||
// @Router /installation/init [post]
|
||||
func InitEnvironment(ctx *gin.Context) {
|
||||
req := &CheckDatabaseReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
// check config file if exist
|
||||
if cli.CheckConfigFile(confPath) {
|
||||
log.Debug("config file already exists")
|
||||
handler.HandleResponse(ctx, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cli.InstallConfigFile(confPath); err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallConfigFailed), &InitEnvironmentResp{
|
||||
Success: false,
|
||||
CreateConfigFailed: true,
|
||||
DefaultConfig: string(configs.Config),
|
||||
ErrType: schema.ErrTypeAlert.ErrType,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c, err := conf.ReadConfig(confPath)
|
||||
if err != nil {
|
||||
log.Errorf("read config failed %s", err)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.ReadConfigFailed), nil)
|
||||
return
|
||||
}
|
||||
c.Data.Database.Driver = req.DbType
|
||||
c.Data.Database.Connection = req.GetConnection()
|
||||
c.Data.Cache.FilePath = filepath.Join(path.CacheDir, path.DefaultCacheFileName)
|
||||
c.I18n.BundleDir = path.I18nPath
|
||||
c.ServiceConfig.UploadPath = path.UploadFilePath
|
||||
|
||||
if err := conf.RewriteConfig(confPath, c); err != nil {
|
||||
log.Errorf("rewrite config failed %s", err)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.ReadConfigFailed), nil)
|
||||
return
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, nil)
|
||||
}
|
||||
|
||||
// InitBaseInfo init base info
|
||||
// @Summary init base info
|
||||
// @Description init base info
|
||||
// @Tags installation
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body install.InitBaseInfoReq true "InitBaseInfoReq"
|
||||
// @Success 200 {object} handler.RespBody{}
|
||||
// @Router /installation/base-info [post]
|
||||
func InitBaseInfo(ctx *gin.Context) {
|
||||
req := &InitBaseInfoReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
req.FormatSiteUrl()
|
||||
|
||||
c, err := conf.ReadConfig(confPath)
|
||||
if err != nil {
|
||||
log.Errorf("read config failed %s", err)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.ReadConfigFailed), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if cli.CheckDBTableExist(c.Data.Database) {
|
||||
log.Warn("database is already initialized")
|
||||
handler.HandleResponse(ctx, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
engine, err := data.NewDB(false, c.Data.Database)
|
||||
if err != nil {
|
||||
log.Errorf("init database failed %s", err)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallCreateTableFailed), nil)
|
||||
}
|
||||
|
||||
inputData := &migrations.InitNeedUserInputData{}
|
||||
_ = copier.Copy(inputData, req)
|
||||
if err := migrations.NewMentor(ctx, engine, inputData).InitDB(); err != nil {
|
||||
log.Error("init database error: ", err.Error())
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallConfigFailed), schema.ErrTypeAlert)
|
||||
return
|
||||
}
|
||||
|
||||
handler.HandleResponse(ctx, nil, nil)
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package install
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type Env struct {
|
||||
AutoInstall string `json:"auto_install"`
|
||||
DbType string `json:"db_type"`
|
||||
DbUsername string `json:"db_username"`
|
||||
DbPassword string `json:"db_password"`
|
||||
DbHost string `json:"db_host"`
|
||||
DbName string `json:"db_name"`
|
||||
DbFile string `json:"db_file"`
|
||||
Language string `json:"lang"`
|
||||
|
||||
SiteName string `json:"site_name"`
|
||||
SiteURL string `json:"site_url"`
|
||||
ContactEmail string `json:"contact_email"`
|
||||
AdminName string `json:"name"`
|
||||
AdminPassword string `json:"password"`
|
||||
AdminEmail string `json:"email"`
|
||||
LoginRequired bool `json:"login_required"`
|
||||
ExternalContentDisplay string `json:"external_content_display"`
|
||||
}
|
||||
|
||||
func TryToInstallByEnv() (installByEnv bool, err error) {
|
||||
env := loadEnv()
|
||||
if len(env.AutoInstall) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
fmt.Println("[auto-install] try to install by environment variable")
|
||||
return true, initByEnv(env)
|
||||
}
|
||||
|
||||
func loadEnv() (env *Env) {
|
||||
return &Env{
|
||||
AutoInstall: os.Getenv("AUTO_INSTALL"),
|
||||
DbType: os.Getenv("DB_TYPE"),
|
||||
DbUsername: os.Getenv("DB_USERNAME"),
|
||||
DbPassword: os.Getenv("DB_PASSWORD"),
|
||||
DbHost: os.Getenv("DB_HOST"),
|
||||
DbName: os.Getenv("DB_NAME"),
|
||||
DbFile: os.Getenv("DB_FILE"),
|
||||
Language: os.Getenv("LANGUAGE"),
|
||||
SiteName: os.Getenv("SITE_NAME"),
|
||||
SiteURL: os.Getenv("SITE_URL"),
|
||||
ContactEmail: os.Getenv("CONTACT_EMAIL"),
|
||||
AdminName: os.Getenv("ADMIN_NAME"),
|
||||
AdminPassword: os.Getenv("ADMIN_PASSWORD"),
|
||||
AdminEmail: os.Getenv("ADMIN_EMAIL"),
|
||||
ExternalContentDisplay: os.Getenv("EXTERNAL_CONTENT_DISPLAY"),
|
||||
}
|
||||
}
|
||||
|
||||
func initByEnv(env *Env) (err error) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
if err = dbCheck(env); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = initConfigAndDb(env); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = initBaseInfo(env); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dbCheck(env *Env) (err error) {
|
||||
req := &CheckDatabaseReq{
|
||||
DbType: env.DbType,
|
||||
DbUsername: env.DbUsername,
|
||||
DbPassword: env.DbPassword,
|
||||
DbHost: env.DbHost,
|
||||
DbName: env.DbName,
|
||||
DbFile: env.DbFile,
|
||||
}
|
||||
return requestAPI(req, "POST", "/installation/db/check", CheckDatabase)
|
||||
}
|
||||
|
||||
func initConfigAndDb(env *Env) (err error) {
|
||||
req := &CheckDatabaseReq{
|
||||
DbType: env.DbType,
|
||||
DbUsername: env.DbUsername,
|
||||
DbPassword: env.DbPassword,
|
||||
DbHost: env.DbHost,
|
||||
DbName: env.DbName,
|
||||
DbFile: env.DbFile,
|
||||
}
|
||||
return requestAPI(req, "POST", "/installation/init", InitEnvironment)
|
||||
}
|
||||
|
||||
func initBaseInfo(env *Env) (err error) {
|
||||
req := &InitBaseInfoReq{
|
||||
Language: env.Language,
|
||||
SiteName: env.SiteName,
|
||||
SiteURL: env.SiteURL,
|
||||
ContactEmail: env.ContactEmail,
|
||||
AdminName: env.AdminName,
|
||||
AdminPassword: env.AdminPassword,
|
||||
AdminEmail: env.AdminEmail,
|
||||
LoginRequired: env.LoginRequired,
|
||||
ExternalContentDisplay: env.ExternalContentDisplay,
|
||||
}
|
||||
return requestAPI(req, "POST", "/installation/base-info", InitBaseInfo)
|
||||
}
|
||||
|
||||
func requestAPI(req any, method, url string, handlerFunc gin.HandlerFunc) error {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
body, _ := json.Marshal(req)
|
||||
c.Request, _ = http.NewRequest(method, url, bytes.NewBuffer(body))
|
||||
if method == "POST" {
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
handlerFunc(c)
|
||||
if w.Code != http.StatusOK {
|
||||
return errors.New(gjson.Get(w.Body.String(), "msg").String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package install
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/apache/answer/internal/base/path"
|
||||
"github.com/apache/answer/internal/base/translator"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var (
|
||||
port string
|
||||
confPath = ""
|
||||
)
|
||||
|
||||
func init() {
|
||||
_ = godotenv.Load()
|
||||
port = os.Getenv("INSTALL_PORT")
|
||||
}
|
||||
|
||||
func Run(configPath string) {
|
||||
confPath = configPath
|
||||
// initialize translator for return internationalization error when installing.
|
||||
_, err := translator.NewTranslator(&translator.I18n{BundleDir: path.I18nPath})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// try to install by env
|
||||
if installByEnv, err := TryToInstallByEnv(); installByEnv && err != nil {
|
||||
fmt.Printf("[auto-install] try to init by env fail: %v\n", err)
|
||||
}
|
||||
|
||||
installServer := NewInstallHTTPServer()
|
||||
if len(port) == 0 {
|
||||
port = "80"
|
||||
}
|
||||
fmt.Printf("[SUCCESS] answer installation service will run at: http://localhost:%s/install/ \n", port)
|
||||
if err = installServer.Run(":" + port); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package install
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/base/validator"
|
||||
"github.com/apache/answer/pkg/checker"
|
||||
"github.com/apache/answer/pkg/dir"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// CheckConfigFileResp check config file if exist or not response
|
||||
type CheckConfigFileResp struct {
|
||||
ConfigFileExist bool `json:"config_file_exist"`
|
||||
DBConnectionSuccess bool `json:"db_connection_success"`
|
||||
DbTableExist bool `json:"db_table_exist"`
|
||||
}
|
||||
|
||||
// CheckDatabaseReq check database
|
||||
type CheckDatabaseReq struct {
|
||||
DbType string `validate:"required,oneof=postgres sqlite3 mysql" json:"db_type"`
|
||||
DbUsername string `json:"db_username"`
|
||||
DbPassword string `json:"db_password"`
|
||||
DbHost string `json:"db_host"`
|
||||
DbName string `json:"db_name"`
|
||||
DbFile string `json:"db_file"`
|
||||
Ssl bool `json:"ssl_enabled"`
|
||||
SslMode string `json:"ssl_mode"`
|
||||
SslRootCert string `json:"ssl_root_cert"`
|
||||
SslKey string `json:"ssl_key"`
|
||||
SslCert string `json:"ssl_cert"`
|
||||
}
|
||||
|
||||
// GetConnection get connection string
|
||||
func (r *CheckDatabaseReq) GetConnection() string {
|
||||
if r.DbType == string(schemas.SQLITE) {
|
||||
return r.DbFile
|
||||
}
|
||||
if r.DbType == string(schemas.MYSQL) {
|
||||
return fmt.Sprintf("%s:%s@tcp(%s)/%s",
|
||||
r.DbUsername, r.DbPassword, r.DbHost, r.DbName)
|
||||
}
|
||||
if r.DbType == string(schemas.POSTGRES) {
|
||||
host, port := parsePgSQLHostPort(r.DbHost)
|
||||
switch {
|
||||
case !r.Ssl:
|
||||
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||
host, port, r.DbUsername, r.DbPassword, r.DbName)
|
||||
case r.SslMode == "require":
|
||||
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
||||
host, port, r.DbUsername, r.DbPassword, r.DbName, r.SslMode)
|
||||
case r.SslMode == "verify-ca" || r.SslMode == "verify-full":
|
||||
connection := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
||||
host, port, r.DbUsername, r.DbPassword, r.DbName, r.SslMode)
|
||||
if len(r.SslRootCert) > 0 && dir.CheckFileExist(r.SslRootCert) {
|
||||
connection += fmt.Sprintf(" sslrootcert=%s", r.SslRootCert)
|
||||
}
|
||||
if len(r.SslCert) > 0 && dir.CheckFileExist(r.SslCert) {
|
||||
connection += fmt.Sprintf(" sslcert=%s", r.SslCert)
|
||||
}
|
||||
if len(r.SslKey) > 0 && dir.CheckFileExist(r.SslKey) {
|
||||
connection += fmt.Sprintf(" sslkey=%s", r.SslKey)
|
||||
}
|
||||
return connection
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parsePgSQLHostPort(dbHost string) (host string, port string) {
|
||||
if strings.Contains(dbHost, ":") {
|
||||
idx := strings.LastIndex(dbHost, ":")
|
||||
host, port = dbHost[:idx], dbHost[idx+1:]
|
||||
} else if len(dbHost) > 0 {
|
||||
host = dbHost
|
||||
}
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
if port == "" {
|
||||
port = "5432"
|
||||
}
|
||||
return host, port
|
||||
}
|
||||
|
||||
// CheckDatabaseResp check database response
|
||||
type CheckDatabaseResp struct {
|
||||
ConnectionSuccess bool `json:"connection_success"`
|
||||
}
|
||||
|
||||
// InitEnvironmentResp init environment response
|
||||
type InitEnvironmentResp struct {
|
||||
Success bool `json:"success"`
|
||||
CreateConfigFailed bool `json:"create_config_failed"`
|
||||
DefaultConfig string `json:"default_config"`
|
||||
ErrType string `json:"err_type"`
|
||||
}
|
||||
|
||||
// InitBaseInfoReq init base info request
|
||||
type InitBaseInfoReq struct {
|
||||
Language string `validate:"required,gt=0,lte=30" json:"lang"`
|
||||
SiteName string `validate:"required,sanitizer,gt=0,lte=30" json:"site_name"`
|
||||
SiteURL string `validate:"required,gt=0,lte=512,url" json:"site_url"`
|
||||
ContactEmail string `validate:"required,email,gt=0,lte=500" json:"contact_email"`
|
||||
AdminName string `validate:"required,gte=2,lte=30" json:"name"`
|
||||
AdminPassword string `validate:"required,gte=8,lte=32" json:"password"`
|
||||
AdminEmail string `validate:"required,email,gt=0,lte=500" json:"email"`
|
||||
LoginRequired bool `json:"login_required"`
|
||||
ExternalContentDisplay string `validate:"required,oneof=always_display ask_before_display" json:"external_content_display"`
|
||||
}
|
||||
|
||||
func (r *InitBaseInfoReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
if checker.IsInvalidUsername(r.AdminName) {
|
||||
errField := &validator.FormErrorField{
|
||||
ErrorField: "name",
|
||||
ErrorMsg: reason.UsernameInvalid,
|
||||
}
|
||||
errFields = append(errFields, errField)
|
||||
return errFields, errors.BadRequest(reason.UsernameInvalid)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *InitBaseInfoReq) FormatSiteUrl() {
|
||||
parsedUrl, err := url.Parse(r.SiteURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r.SiteURL = fmt.Sprintf("%s://%s", parsedUrl.Scheme, parsedUrl.Host)
|
||||
if len(parsedUrl.Path) > 0 {
|
||||
r.SiteURL += parsedUrl.Path
|
||||
r.SiteURL = strings.TrimSuffix(r.SiteURL, "/")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package install
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
|
||||
"github.com/apache/answer/configs"
|
||||
"github.com/apache/answer/internal/base/conf"
|
||||
"github.com/apache/answer/ui"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const UIStaticPath = "build/static"
|
||||
|
||||
type _resource struct {
|
||||
fs embed.FS
|
||||
}
|
||||
|
||||
// Open to implement the interface by http.FS required
|
||||
func (r *_resource) Open(name string) (fs.File, error) {
|
||||
name = fmt.Sprintf(UIStaticPath+"/%s", name)
|
||||
log.Debugf("open static path %s", name)
|
||||
return r.fs.Open(name)
|
||||
}
|
||||
|
||||
// NewInstallHTTPServer new install http server.
|
||||
func NewInstallHTTPServer() *gin.Engine {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.New()
|
||||
|
||||
c := &conf.AllConfig{}
|
||||
_ = yaml.Unmarshal(configs.Config, c)
|
||||
|
||||
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
|
||||
r.StaticFS(c.UI.BaseURL+"/static", http.FS(&_resource{
|
||||
fs: ui.Build,
|
||||
}))
|
||||
|
||||
// read default config file and extract ui config
|
||||
installApi := r.Group("")
|
||||
installApi.GET(c.UI.BaseURL+"/", CheckConfigFileAndRedirectToInstallPage)
|
||||
installApi.GET(c.UI.BaseURL+"/install", WebPage)
|
||||
installApi.GET(c.UI.BaseURL+"/50x", WebPage)
|
||||
installApi.GET(c.UI.APIBaseURL+"/installation/language/config", GetLangMapping)
|
||||
installApi.GET(c.UI.APIBaseURL+"/installation/language/options", LangOptions)
|
||||
installApi.POST(c.UI.APIBaseURL+"/installation/db/check", CheckDatabase)
|
||||
installApi.POST(c.UI.APIBaseURL+"/installation/config-file/check", CheckConfigFile)
|
||||
installApi.POST(c.UI.APIBaseURL+"/installation/init", InitEnvironment)
|
||||
installApi.POST(c.UI.APIBaseURL+"/installation/base-info", InitBaseInfo)
|
||||
|
||||
r.NoRoute(func(ctx *gin.Context) {
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func WebPage(c *gin.Context) {
|
||||
filePath := ""
|
||||
var file []byte
|
||||
var err error
|
||||
filePath = "build/index.html"
|
||||
c.Header("content-type", "text/html;charset=utf-8")
|
||||
file, err = ui.Build.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
c.String(http.StatusOK, string(file))
|
||||
}
|
||||
Reference in New Issue
Block a user