mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-28 17:13:08 +00:00
chore: add demo mode
This commit is contained in:
parent
af31875e6a
commit
bd9daddaef
@ -82,7 +82,7 @@ func Execute() error {
|
|||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
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().IntVarP(&port, "port", "p", 8082, "port of server")
|
||||||
rootCmd.PersistentFlags().StringVarP(&data, "data", "d", "", "data directory")
|
rootCmd.PersistentFlags().StringVarP(&data, "data", "d", "", "data directory")
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ func init() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.SetDefault("mode", "dev")
|
viper.SetDefault("mode", "demo")
|
||||||
viper.SetDefault("port", 8082)
|
viper.SetDefault("port", 8082)
|
||||||
viper.SetEnvPrefix("slash")
|
viper.SetEnvPrefix("slash")
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,8 @@ func GetProfile() (*Profile, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile.Mode != "dev" && profile.Mode != "prod" {
|
if profile.Mode != "demo" && profile.Mode != "dev" && profile.Mode != "prod" {
|
||||||
profile.Mode = "dev"
|
profile.Mode = "demo"
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile.Mode == "prod" && profile.Data == "" {
|
if profile.Mode == "prod" && profile.Data == "" {
|
||||||
|
@ -15,7 +15,7 @@ var Version = "0.1.0"
|
|||||||
var DevVersion = "0.1.0"
|
var DevVersion = "0.1.0"
|
||||||
|
|
||||||
func GetCurrentVersion(mode string) string {
|
func GetCurrentVersion(mode string) string {
|
||||||
if mode == "dev" {
|
if mode == "dev" || mode == "demo" {
|
||||||
return DevVersion
|
return DevVersion
|
||||||
}
|
}
|
||||||
return Version
|
return Version
|
||||||
|
@ -19,6 +19,9 @@ import (
|
|||||||
//go:embed migration
|
//go:embed migration
|
||||||
var migrationFS embed.FS
|
var migrationFS embed.FS
|
||||||
|
|
||||||
|
//go:embed seed
|
||||||
|
var seedFS embed.FS
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
profile *profile.Profile
|
profile *profile.Profile
|
||||||
// sqlite db connection instance
|
// sqlite db connection instance
|
||||||
@ -119,6 +122,12 @@ func (db *DB) Open(ctx context.Context) (err error) {
|
|||||||
if err := db.applyLatestSchema(ctx); err != nil {
|
if err := db.applyLatestSchema(ctx); err != nil {
|
||||||
return fmt.Errorf("failed to apply latest schema: %w", err)
|
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()
|
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.
|
// execute runs a single SQL statement within a transaction.
|
||||||
func (db *DB) execute(ctx context.Context, stmt string) error {
|
func (db *DB) execute(ctx context.Context, stmt string) error {
|
||||||
tx, err := db.DBInstance.Begin()
|
tx, err := db.DBInstance.Begin()
|
||||||
|
9
store/db/seed/10000__reset.sql
Normal file
9
store/db/seed/10000__reset.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
DELETE FROM activity;
|
||||||
|
|
||||||
|
DELETE FROM shortcut;
|
||||||
|
|
||||||
|
DELETE FROM user_setting;
|
||||||
|
|
||||||
|
DELETE FROM user;
|
||||||
|
|
||||||
|
DELETE FROM workspace_setting;
|
35
store/db/seed/10001__user.sql
Normal file
35
store/db/seed/10001__user.sql
Normal file
@ -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'
|
||||||
|
);
|
67
store/db/seed/10002__shortcut.sql
Normal file
67
store/db/seed/10002__shortcut.sql
Normal file
@ -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'
|
||||||
|
);
|
31
web/src/components/DemoBanner.tsx
Normal file
31
web/src/components/DemoBanner.tsx
Normal file
@ -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 (
|
||||||
|
<div className="z-10 flex flex-row items-center justify-center w-full py-2 text-sm sm:text-lg font-medium dark:text-gray-300 bg-white dark:bg-zinc-700 shadow">
|
||||||
|
<div className="w-full max-w-4xl px-4 flex flex-row justify-between items-center gap-x-3">
|
||||||
|
<span>✨A bookmarking and url shortener, save and share your links very easily.✨</span>
|
||||||
|
<a
|
||||||
|
className="shadow flex flex-row justify-center items-center px-2 py-1 rounded-md text-sm sm:text-base text-white bg-blue-600 hover:bg-blue-700"
|
||||||
|
href="https://github.com/boojack/slash#deploy-with-docker-in-seconds"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Install
|
||||||
|
<Icon.ExternalLink className="w-4 h-auto ml-1" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DemoBanner;
|
@ -95,7 +95,7 @@ const ShortcutView = (props: Props) => {
|
|||||||
<div className="flex flex-row justify-end items-center space-x-2">
|
<div className="flex flex-row justify-end items-center space-x-2">
|
||||||
{havePermission && (
|
{havePermission && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
actionsClassName="!w-24"
|
actionsClassName="!w-32"
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
@ -104,6 +104,12 @@ const ShortcutView = (props: Props) => {
|
|||||||
>
|
>
|
||||||
<Icon.Edit className="w-4 h-auto mr-2" /> Edit
|
<Icon.Edit className="w-4 h-auto mr-2" /> Edit
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="w-full px-2 flex flex-row justify-start items-center text-left leading-8 cursor-pointer rounded hover:bg-gray-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
|
||||||
|
onClick={() => setShowAnalyticsDialog(true)}
|
||||||
|
>
|
||||||
|
<Icon.BarChart2 className="w-4 h-auto mr-2" /> Analytics
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className="w-full px-2 flex flex-row justify-start items-center text-left leading-8 cursor-pointer rounded text-red-600 hover:bg-gray-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
|
className="w-full px-2 flex flex-row justify-start items-center text-left leading-8 cursor-pointer rounded text-red-600 hover:bg-gray-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -2,6 +2,7 @@ import { useEffect } from "react";
|
|||||||
import { Outlet, useNavigate } from "react-router-dom";
|
import { Outlet, useNavigate } from "react-router-dom";
|
||||||
import useUserStore from "../stores/v1/user";
|
import useUserStore from "../stores/v1/user";
|
||||||
import Header from "../components/Header";
|
import Header from "../components/Header";
|
||||||
|
import DemoBanner from "../components/DemoBanner";
|
||||||
|
|
||||||
const Root: React.FC = () => {
|
const Root: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -19,6 +20,7 @@ const Root: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
{currentUser && (
|
{currentUser && (
|
||||||
<div className="w-full h-full flex flex-col justify-start items-start">
|
<div className="w-full h-full flex flex-col justify-start items-start">
|
||||||
|
<DemoBanner />
|
||||||
<Header />
|
<Header />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,10 @@ const SignIn: React.FC = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const {
|
const {
|
||||||
workspaceProfile: { disallowSignUp },
|
workspaceProfile: {
|
||||||
|
disallowSignUp,
|
||||||
|
profile: { mode },
|
||||||
|
},
|
||||||
} = useAppSelector((state) => state.global);
|
} = useAppSelector((state) => state.global);
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@ -24,6 +27,11 @@ const SignIn: React.FC = () => {
|
|||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode === "demo") {
|
||||||
|
setEmail("slash@stevenlgtm.com");
|
||||||
|
setPassword("secret");
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleEmailInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleEmailInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user