diff --git a/cmd/slash/main.go b/cmd/slash/main.go index 93dd0ef..3bfcf0e 100644 --- a/cmd/slash/main.go +++ b/cmd/slash/main.go @@ -82,7 +82,7 @@ func Execute() error { func init() { cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVarP(&mode, "mode", "m", "dev", `mode of server, can be "prod" or "dev"`) + rootCmd.PersistentFlags().StringVarP(&mode, "mode", "m", "demo", `mode of server, can be "prod" or "dev" or "demo"`) rootCmd.PersistentFlags().IntVarP(&port, "port", "p", 8082, "port of server") rootCmd.PersistentFlags().StringVarP(&data, "data", "d", "", "data directory") @@ -99,7 +99,7 @@ func init() { panic(err) } - viper.SetDefault("mode", "dev") + viper.SetDefault("mode", "demo") viper.SetDefault("port", 8082) viper.SetEnvPrefix("slash") } diff --git a/server/profile/profile.go b/server/profile/profile.go index 0bc6eaa..03ff330 100644 --- a/server/profile/profile.go +++ b/server/profile/profile.go @@ -58,8 +58,8 @@ func GetProfile() (*Profile, error) { return nil, err } - if profile.Mode != "dev" && profile.Mode != "prod" { - profile.Mode = "dev" + if profile.Mode != "demo" && profile.Mode != "dev" && profile.Mode != "prod" { + profile.Mode = "demo" } if profile.Mode == "prod" && profile.Data == "" { diff --git a/server/version/version.go b/server/version/version.go index c34abe4..7941fd3 100644 --- a/server/version/version.go +++ b/server/version/version.go @@ -15,7 +15,7 @@ var Version = "0.1.0" var DevVersion = "0.1.0" func GetCurrentVersion(mode string) string { - if mode == "dev" { + if mode == "dev" || mode == "demo" { return DevVersion } return Version diff --git a/store/db/db.go b/store/db/db.go index f110789..9803dec 100644 --- a/store/db/db.go +++ b/store/db/db.go @@ -19,6 +19,9 @@ import ( //go:embed migration var migrationFS embed.FS +//go:embed seed +var seedFS embed.FS + type DB struct { profile *profile.Profile // sqlite db connection instance @@ -119,6 +122,12 @@ func (db *DB) Open(ctx context.Context) (err error) { if err := db.applyLatestSchema(ctx); err != nil { return fmt.Errorf("failed to apply latest schema: %w", err) } + // In demo mode, we should seed the database. + if db.profile.Mode == "demo" { + if err := db.seed(ctx); err != nil { + return fmt.Errorf("failed to seed: %w", err) + } + } } } @@ -185,6 +194,28 @@ func (db *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion st return tx.Commit() } +func (db *DB) seed(ctx context.Context) error { + filenames, err := fs.Glob(seedFS, fmt.Sprintf("%s/*.sql", "seed")) + if err != nil { + return fmt.Errorf("failed to read seed files, err: %w", err) + } + + sort.Strings(filenames) + + // Loop over all seed files and execute them in order. + for _, filename := range filenames { + buf, err := seedFS.ReadFile(filename) + if err != nil { + return fmt.Errorf("failed to read seed file, filename=%s err=%w", filename, err) + } + stmt := string(buf) + if err := db.execute(ctx, stmt); err != nil { + return fmt.Errorf("seed error: statement:%s err=%w", stmt, err) + } + } + return nil +} + // execute runs a single SQL statement within a transaction. func (db *DB) execute(ctx context.Context, stmt string) error { tx, err := db.DBInstance.Begin() diff --git a/store/db/seed/10000__reset.sql b/store/db/seed/10000__reset.sql new file mode 100644 index 0000000..79be1d8 --- /dev/null +++ b/store/db/seed/10000__reset.sql @@ -0,0 +1,9 @@ +DELETE FROM activity; + +DELETE FROM shortcut; + +DELETE FROM user_setting; + +DELETE FROM user; + +DELETE FROM workspace_setting; diff --git a/store/db/seed/10001__user.sql b/store/db/seed/10001__user.sql new file mode 100644 index 0000000..6c55b5c --- /dev/null +++ b/store/db/seed/10001__user.sql @@ -0,0 +1,35 @@ +INSERT INTO + user ( + `id`, + `role`, + `email`, + `nickname`, + `password_hash` + ) +VALUES + ( + 101, + 'ADMIN', + 'slash@stevenlgtm.com', + 'Slasher', + -- raw password: secret + '$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK' + ); + +INSERT INTO + user ( + `id`, + `role`, + `email`, + `nickname`, + `password_hash` + ) +VALUES + ( + 102, + 'USER', + 'steven@usememos.com', + 'Steven', + -- raw password: secret + '$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK' + ); diff --git a/store/db/seed/10002__shortcut.sql b/store/db/seed/10002__shortcut.sql new file mode 100644 index 0000000..141c266 --- /dev/null +++ b/store/db/seed/10002__shortcut.sql @@ -0,0 +1,67 @@ +INSERT INTO + shortcut ( + `id`, + `creator_id`, + `name`, + `link`, + `visibility` + ) +VALUES + ( + 1, + 101, + 'memos', + 'https://usememos.com', + 'PUBLIC' + ); + +INSERT INTO + shortcut ( + `id`, + `creator_id`, + `name`, + `link`, + `visibility` + ) +VALUES + ( + 2, + 101, + 'sqlchat', + 'https://www.sqlchat.ai', + 'WORKSPACE' + ); + +INSERT INTO + shortcut ( + `id`, + `creator_id`, + `name`, + `link`, + `visibility` + ) +VALUES + ( + 3, + 101, + 'schema-change', + 'https://www.bytebase.com/blog/how-to-handle-database-schema-change/#what-is-a-database-schema-change', + 'PUBLIC' + ); + +INSERT INTO + shortcut ( + `id`, + `creator_id`, + `name`, + `link`, + `visibility` + ) +VALUES + ( + 4, + 102, + 'stevenlgtm', + 'https://github.com/boojack', + 'PUBLIC' + ); diff --git a/web/src/components/DemoBanner.tsx b/web/src/components/DemoBanner.tsx new file mode 100644 index 0000000..1e5024f --- /dev/null +++ b/web/src/components/DemoBanner.tsx @@ -0,0 +1,31 @@ +import { globalService } from "../services"; +import Icon from "./Icon"; + +const DemoBanner: React.FC = () => { + const { + workspaceProfile: { + profile: { mode }, + }, + } = globalService.getState(); + const shouldShow = mode === "demo"; + + if (!shouldShow) return null; + + return ( +