mirror of https://github.com/usememos/memos
				
				
				
			feat: add system setting to allow user signup (#407)
							parent
							
								
									4ed987229b
								
							
						
					
					
						commit
						cf75054106
					
				@ -0,0 +1,70 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SystemSettingName string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// SystemSettingAllowSignUpName is the key type of allow signup setting.
 | 
			
		||||
	SystemSettingAllowSignUpName SystemSettingName = "allowSignUp"
 | 
			
		||||
	SystemSettingPlaceholderName SystemSettingName = "placeholder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (key SystemSettingName) String() string {
 | 
			
		||||
	switch key {
 | 
			
		||||
	case SystemSettingAllowSignUpName:
 | 
			
		||||
		return "allowSignUp"
 | 
			
		||||
	case SystemSettingPlaceholderName:
 | 
			
		||||
		return "placeholder"
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	SystemSettingAllowSignUpValue = []bool{true, false}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SystemSetting struct {
 | 
			
		||||
	Name SystemSettingName
 | 
			
		||||
	// Value is a JSON string with basic value
 | 
			
		||||
	Value       string
 | 
			
		||||
	Description string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SystemSettingUpsert struct {
 | 
			
		||||
	Name        SystemSettingName `json:"name"`
 | 
			
		||||
	Value       string            `json:"value"`
 | 
			
		||||
	Description string            `json:"description"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (upsert SystemSettingUpsert) Validate() error {
 | 
			
		||||
	if upsert.Name == SystemSettingAllowSignUpName {
 | 
			
		||||
		value := false
 | 
			
		||||
		err := json.Unmarshal([]byte(upsert.Value), &value)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to unmarshal system setting allow signup value")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		invalid := true
 | 
			
		||||
		for _, v := range SystemSettingAllowSignUpValue {
 | 
			
		||||
			if value == v {
 | 
			
		||||
				invalid = false
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if invalid {
 | 
			
		||||
			return fmt.Errorf("invalid system setting allow signup value")
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		return fmt.Errorf("invalid system setting name")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SystemSettingFind struct {
 | 
			
		||||
	Name *SystemSettingName `json:"name"`
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
INSERT INTO 
 | 
			
		||||
  system_setting (
 | 
			
		||||
    `name`, 
 | 
			
		||||
    `value`,
 | 
			
		||||
    `description`
 | 
			
		||||
  )
 | 
			
		||||
VALUES
 | 
			
		||||
  (
 | 
			
		||||
    'allowSignUp', 
 | 
			
		||||
    'true',
 | 
			
		||||
    ''
 | 
			
		||||
  );
 | 
			
		||||
@ -0,0 +1,154 @@
 | 
			
		||||
package store
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/usememos/memos/api"
 | 
			
		||||
	"github.com/usememos/memos/common"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type systemSettingRaw struct {
 | 
			
		||||
	Name        api.SystemSettingName
 | 
			
		||||
	Value       string
 | 
			
		||||
	Description string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (raw *systemSettingRaw) toSystemSetting() *api.SystemSetting {
 | 
			
		||||
	return &api.SystemSetting{
 | 
			
		||||
		Name:        raw.Name,
 | 
			
		||||
		Value:       raw.Value,
 | 
			
		||||
		Description: raw.Description,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Store) UpsertSystemSetting(ctx context.Context, upsert *api.SystemSettingUpsert) (*api.SystemSetting, error) {
 | 
			
		||||
	tx, err := s.db.BeginTx(ctx, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, FormatError(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer tx.Rollback()
 | 
			
		||||
 | 
			
		||||
	systemSettingRaw, err := upsertSystemSetting(ctx, tx, upsert)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := tx.Commit(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	systemSetting := systemSettingRaw.toSystemSetting()
 | 
			
		||||
 | 
			
		||||
	return systemSetting, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Store) FindSystemSettingList(ctx context.Context, find *api.SystemSettingFind) ([]*api.SystemSetting, error) {
 | 
			
		||||
	tx, err := s.db.BeginTx(ctx, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, FormatError(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer tx.Rollback()
 | 
			
		||||
 | 
			
		||||
	systemSettingRawList, err := findSystemSettingList(ctx, tx, find)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := tx.Commit(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	list := []*api.SystemSetting{}
 | 
			
		||||
	for _, raw := range systemSettingRawList {
 | 
			
		||||
		list = append(list, raw.toSystemSetting())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return list, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Store) FindSystemSetting(ctx context.Context, find *api.SystemSettingFind) (*api.SystemSetting, error) {
 | 
			
		||||
	tx, err := s.db.BeginTx(ctx, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, FormatError(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer tx.Rollback()
 | 
			
		||||
 | 
			
		||||
	systemSettingRawList, err := findSystemSettingList(ctx, tx, find)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(systemSettingRawList) == 0 {
 | 
			
		||||
		return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return systemSettingRawList[0].toSystemSetting(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func upsertSystemSetting(ctx context.Context, tx *sql.Tx, upsert *api.SystemSettingUpsert) (*systemSettingRaw, error) {
 | 
			
		||||
	query := `
 | 
			
		||||
		INSERT INTO system_setting (
 | 
			
		||||
			name, value, description
 | 
			
		||||
		)
 | 
			
		||||
		VALUES (?, ?, ?)
 | 
			
		||||
		ON CONFLICT(name) DO UPDATE 
 | 
			
		||||
		SET
 | 
			
		||||
			value = EXCLUDED.value,
 | 
			
		||||
			description = EXCLUDED.description
 | 
			
		||||
		RETURNING name, value, description
 | 
			
		||||
	`
 | 
			
		||||
	var systemSettingRaw systemSettingRaw
 | 
			
		||||
	if err := tx.QueryRowContext(ctx, query, upsert.Name, upsert.Value, upsert.Description).Scan(
 | 
			
		||||
		&systemSettingRaw.Name,
 | 
			
		||||
		&systemSettingRaw.Value,
 | 
			
		||||
		&systemSettingRaw.Description,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return nil, FormatError(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &systemSettingRaw, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func findSystemSettingList(ctx context.Context, tx *sql.Tx, find *api.SystemSettingFind) ([]*systemSettingRaw, error) {
 | 
			
		||||
	where, args := []string{"1 = 1"}, []interface{}{}
 | 
			
		||||
 | 
			
		||||
	if v := find.Name; v != nil {
 | 
			
		||||
		where, args = append(where, "name = ?"), append(args, v.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	query := `
 | 
			
		||||
		SELECT
 | 
			
		||||
			name,
 | 
			
		||||
		  value,
 | 
			
		||||
			description
 | 
			
		||||
		FROM system_setting
 | 
			
		||||
		WHERE ` + strings.Join(where, " AND ")
 | 
			
		||||
	rows, err := tx.QueryContext(ctx, query, args...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, FormatError(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer rows.Close()
 | 
			
		||||
 | 
			
		||||
	systemSettingRawList := make([]*systemSettingRaw, 0)
 | 
			
		||||
	for rows.Next() {
 | 
			
		||||
		var systemSettingRaw systemSettingRaw
 | 
			
		||||
		if err := rows.Scan(
 | 
			
		||||
			&systemSettingRaw.Name,
 | 
			
		||||
			&systemSettingRaw.Value,
 | 
			
		||||
			&systemSettingRaw.Description,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return nil, FormatError(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		systemSettingRawList = append(systemSettingRawList, &systemSettingRaw)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := rows.Err(); err != nil {
 | 
			
		||||
		return nil, FormatError(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return systemSettingRawList, nil
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,65 @@
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import Switch from "@mui/joy/Switch";
 | 
			
		||||
import * as api from "../../helpers/api";
 | 
			
		||||
import { globalService, userService } from "../../services";
 | 
			
		||||
import Selector from "../common/Selector";
 | 
			
		||||
import "../../less/settings/preferences-section.less";
 | 
			
		||||
 | 
			
		||||
const localeSelectorItems = [
 | 
			
		||||
  {
 | 
			
		||||
    text: "English",
 | 
			
		||||
    value: "en",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    text: "中文",
 | 
			
		||||
    value: "zh",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    text: "Tiếng Việt",
 | 
			
		||||
    value: "vi",
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
interface State {
 | 
			
		||||
  allowSignUp: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SystemSection = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const [state, setState] = useState<State>({
 | 
			
		||||
    allowSignUp: false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    api.getSystemStatus().then(({ data }) => {
 | 
			
		||||
      const { data: status } = data;
 | 
			
		||||
      setState({
 | 
			
		||||
        allowSignUp: status.allowSignUp,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const handleAllowSignUpChanged = async (value: boolean) => {
 | 
			
		||||
    setState({
 | 
			
		||||
      ...state,
 | 
			
		||||
      allowSignUp: value,
 | 
			
		||||
    });
 | 
			
		||||
    await api.upsertSystemSetting({
 | 
			
		||||
      name: "allowSignUp",
 | 
			
		||||
      value: JSON.stringify(value),
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="section-container preferences-section-container">
 | 
			
		||||
      <p className="title-text">{t("common.basic")}</p>
 | 
			
		||||
      <label className="form-label selector">
 | 
			
		||||
        <span className="normal-text">Allow user signUp</span>
 | 
			
		||||
        <Switch size="sm" checked={state.allowSignUp} onChange={(event) => handleAllowSignUpChanged(event.target.checked)} />
 | 
			
		||||
      </label>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SystemSection;
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue