From 86ef860568fc9ea807b857cae729951f9fc8598e Mon Sep 17 00:00:00 2001 From: juls0730 <62722391+juls0730@users.noreply.github.com> Date: Sun, 8 Sep 2024 07:15:43 -0500 Subject: [PATCH] uploading files and a lot more --- .gitignore | 4 +- main.go | 59 ++++++-- middleware/auth.go | 24 +-- middleware/route.go | 49 +++++++ models/user.go | 22 ++- routes/auth.go | 56 +++---- routes/files.go | 194 +++++++++++++++++++++++++ ui/assets/css/main.css | 42 ++++-- ui/bun.lockb | Bin 291353 -> 296106 bytes ui/components/Breadcrumbs.vue | 33 +++++ ui/components/FileNav.vue | 116 +++++++++++++++ ui/components/Footer.vue | 13 ++ ui/components/Input.vue | 2 +- ui/components/Nav.vue | 67 +++++++++ ui/components/UploadPane.vue | 239 ++++++++++++++++++++++++++++++ ui/composables/useUser.ts | 49 +++++++ ui/middleware/auth.ts | 15 ++ ui/middleware/unauth.ts | 15 ++ ui/pages/home.vue | 12 -- ui/pages/home/[...name].vue | 266 ++++++++++++++++++++++++++++++++++ ui/pages/index.vue | 191 +++++++++++++++++++++++- ui/pages/login.vue | 19 +-- ui/pages/signup.vue | 16 +- ui/public/placeholder.svg | 1 + ui/tailwind.config.js | 3 + ui/types/file.ts | 6 + ui/types/user.ts | 29 ++++ ui/utils/formatBytes.ts | 12 ++ 28 files changed, 1447 insertions(+), 107 deletions(-) create mode 100644 middleware/route.go create mode 100644 routes/files.go create mode 100644 ui/components/Breadcrumbs.vue create mode 100644 ui/components/FileNav.vue create mode 100644 ui/components/Footer.vue create mode 100644 ui/components/UploadPane.vue create mode 100644 ui/composables/useUser.ts create mode 100644 ui/middleware/auth.ts create mode 100644 ui/middleware/unauth.ts delete mode 100644 ui/pages/home.vue create mode 100644 ui/pages/home/[...name].vue create mode 100644 ui/public/placeholder.svg create mode 100644 ui/types/file.ts create mode 100644 ui/types/user.ts create mode 100644 ui/utils/formatBytes.ts diff --git a/.gitignore b/.gitignore index fb18888..9e01575 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ filething # nuxt buildCache is bugged and weird on my setup -node_modules \ No newline at end of file +node_modules + +data/ \ No newline at end of file diff --git a/main.go b/main.go index 53537b8..8340b10 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ //go:generate bun --cwd=./ui install -//go:generate bun --cwd=./ui run generate +//go:generate bun --bun --cwd=./ui run generate package main import ( @@ -10,6 +10,7 @@ import ( "filething/routes" "filething/ui" "fmt" + "log" "net/http" "os" "strings" @@ -27,7 +28,7 @@ func main() { dbUser := os.Getenv("DB_USER") dbPasswd := os.Getenv("DB_PASSWD") - if dbHost == "" || dbName == "" || dbUser == "" { + if dbHost == "" || dbName == "" || dbUser == "" || os.Getenv("STORAGE_PATH") == "" { panic("Missing database environment variabled!") } @@ -41,6 +42,11 @@ func main() { panic(err) } + err = seedPlans(db) + if err != nil { + panic(err) + } + e := echo.New() e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { @@ -64,17 +70,20 @@ func main() { { api.POST("/login", routes.LoginHandler) api.POST("/signup", routes.SignupHandler) + + // everything past this needs auth api.Use(middleware.SessionMiddleware(db)) - api.GET("/user", func(c echo.Context) error { - user := c.Get("user").(*models.User) - message := fmt.Sprintf("You are %s", user.ID) - return c.JSON(http.StatusOK, map[string]string{"message": message}) - }) - api.GET("/hello", func(c echo.Context) error { - return c.JSON(http.StatusOK, map[string]string{"message": "Hello, World!!!"}) - }) + api.GET("/user", routes.GetUser) + api.GET("/user/usage", routes.GetUsage) + + api.POST("/upload*", routes.UploadFile) + api.GET("/files*", routes.GetFiles) } + // redirects to the proper pages if you are trying to access one that expects you have/dont have an api key + // this isnt explicitly required, but it provides a better experience than doing this same thing clientside + e.Use(middleware.AuthCheckMiddleware) + e.GET("/*", echo.StaticDirectoryHandler(ui.DistDirFS, false)) e.HTTPErrorHandler = customHTTPErrorHandler @@ -83,8 +92,6 @@ func main() { } func customHTTPErrorHandler(err error, c echo.Context) { - c.Logger().Error(err) - if he, ok := err.(*echo.HTTPError); ok && he.Code == http.StatusNotFound { path := c.Request().URL.Path @@ -123,6 +130,7 @@ func createSchema(db *bun.DB) error { models := []interface{}{ (*models.User)(nil), (*models.Session)(nil), + (*models.Plan)(nil), } ctx := context.Background() @@ -134,3 +142,30 @@ func createSchema(db *bun.DB) error { } return nil } + +func seedPlans(db *bun.DB) error { + ctx := context.Background() + count, err := db.NewSelect().Model((*models.Plan)(nil)).Count(ctx) + if err != nil { + return fmt.Errorf("failed to count plans: %w", err) + } + + // If the table is not empty, no need to seed + if count > 0 { + return nil + } + + plans := []models.Plan{ + {MaxStorage: 10 * 1024 * 1024 * 1024}, // 10GB + {MaxStorage: 50 * 1024 * 1024 * 1024}, // 50GB + {MaxStorage: 100 * 1024 * 1024 * 1024}, // 100GB + } + + _, err = db.NewInsert().Model(&plans).Exec(ctx) + if err != nil { + return fmt.Errorf("failed to seed plans: %w", err) + } + + log.Println("Successfully seeded the plans table") + return nil +} diff --git a/middleware/auth.go b/middleware/auth.go index c18e342..84b7e09 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -12,15 +12,6 @@ import ( "github.com/uptrace/bun" ) -// import ( -// "database/sql" -// "net/http" - -// "github.com/go-pg/pg/v10" - -// "github.com/labstack/echo/v4" -// ) - const UserContextKey = "user" func SessionMiddleware(db *bun.DB) echo.MiddlewareFunc { @@ -46,7 +37,7 @@ func SessionMiddleware(db *bun.DB) echo.MiddlewareFunc { session := &models.Session{ ID: sessionId, } - err = db.NewSelect().Model(session).Relation("User").WherePK().Scan(context.Background()) + err = db.NewSelect().Model(session).WherePK().Scan(context.Background()) if err != nil { fmt.Println(err) @@ -56,7 +47,18 @@ func SessionMiddleware(db *bun.DB) echo.MiddlewareFunc { return echo.NewHTTPError(http.StatusInternalServerError, "Database error") } - user := &session.User + user := &models.User{ + ID: session.UserID, + } + err = db.NewSelect().Model(user).Relation("Plan").WherePK().Scan(context.Background()) + + if err != nil { + if err == sql.ErrNoRows { + return echo.NewHTTPError(http.StatusUnauthorized, "Invalid session token") + } + fmt.Println(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Database error") + } // Store the user in the context c.Set(UserContextKey, user) diff --git a/middleware/route.go b/middleware/route.go new file mode 100644 index 0000000..ffcfe6b --- /dev/null +++ b/middleware/route.go @@ -0,0 +1,49 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/labstack/echo/v4" +) + +var unauthenticatedPages = []string{ + "/login", + "/signup", + "/", +} + +var authenticatedPages = []string{ + "/home", +} + +func AuthCheckMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + path := c.Request().URL.Path + _, cookieErr := c.Cookie("sessionToken") + authenticated := cookieErr == nil + + if Contains(unauthenticatedPages, path) && authenticated { + return c.Redirect(http.StatusFound, "/home") + } + + if Contains(authenticatedPages, path) && !authenticated { + return c.Redirect(http.StatusFound, "/login") + } + + if strings.Contains(path, "/home") && !authenticated { + return c.Redirect(http.StatusFound, "/login") + } + + return next(c) + } +} + +func Contains(s []string, element string) bool { + for _, v := range s { + if v == element { + return true + } + } + return false +} diff --git a/models/user.go b/models/user.go index 58d5b2f..a6e04a5 100644 --- a/models/user.go +++ b/models/user.go @@ -17,16 +17,24 @@ type SignupData struct { } type User struct { - bun.BaseModel `bun:"table:users,alias:u"` - ID uuid.UUID `bun:",pk,type:uuid,default:uuid_generate_v4()"` - Username string `bun:"username,notnull,unique"` - Email string `bun:"email,notnull,unique"` - PasswordHash string `bun:"passwordHash,notnull"` + bun.BaseModel `bun:"table:users"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id"` + Username string `bun:"username,notnull,unique" json:"username"` + Email string `bun:"email,notnull,unique" json:"email"` + PasswordHash string `bun:"passwordHash,notnull" json:"-"` + PlanID int64 `bun:"plan_id,notnull" json:"-"` + Plan Plan `bun:"rel:belongs-to,join:plan_id=id" json:"plan"` } type Session struct { - bun.BaseModel `bun:"table:sessions,alias:u"` - ID uuid.UUID `bun:",pk,type:uuid,default:uuid_generate_v4()"` + bun.BaseModel `bun:"table:sessions"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` UserID uuid.UUID `bun:"user_id,notnull,type:uuid"` User User `bun:"rel:belongs-to,join:user_id=id"` } + +type Plan struct { + bun.BaseModel `bun:"table:plans"` + ID int64 `bun:"id,pk,autoincrement" json:"id"` + MaxStorage int64 `bun:"max_storage,notnull" json:"max_storage"` +} diff --git a/routes/auth.go b/routes/auth.go index e3c7d56..ed76e98 100644 --- a/routes/auth.go +++ b/routes/auth.go @@ -3,8 +3,11 @@ package routes import ( "context" "filething/models" + "fmt" "net/http" + "os" "regexp" + "time" "github.com/google/uuid" "github.com/labstack/echo/v4" @@ -40,33 +43,17 @@ func LoginHandler(c echo.Context) error { return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) } + expiration := time.Now().Add(time.Hour * 24 * 365 * 100) + c.SetCookie(&http.Cookie{ Name: "sessionToken", Value: session.ID.String(), SameSite: http.SameSiteStrictMode, + Expires: expiration, Path: "/", }) return c.JSON(http.StatusOK, map[string]string{"message": "Login successful!"}) - - // sessionID := uuid.New().String() - // session := &models.Session{ID: sessionID, UserID: user.ID, ExpiresAt: time.Now().Add(time.Hour * 24)} - - // key := "session:" + session.ID - // err = client.HSet(ctx, key, session).Err() - - // if err != nil { - // c.JSON(http.StatusInternalServerError, gin.H{"message": "An unknown error occoured!"}) - // return - // } - - // http.SetCookie(c.Writer, &http.Cookie{ - // Name: "sessionToken", - // Value: sessionID, - // Path: "/", - // }) - - // c.JSON(http.StatusOK, gin.H{"message": "Login successful"}) } func SignupHandler(c echo.Context) error { @@ -96,6 +83,7 @@ func SignupHandler(c echo.Context) error { Username: signupData.Username, Email: signupData.Email, PasswordHash: string(hash), + PlanID: 1, // basic 10GB plan } _, err = db.NewInsert().Model(user).Exec(context.Background()) @@ -103,28 +91,33 @@ func SignupHandler(c echo.Context) error { return c.JSON(http.StatusConflict, map[string]string{"message": "A user with that email or username already exists!"}) } + err = os.Mkdir(fmt.Sprintf("%s/%s", os.Getenv("STORAGE_PATH"), user.ID), os.ModePerm) + if err != nil { + fmt.Println(err) + return err + } + + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + } + session, err := GenerateSessionToken(db, user.ID) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) } + expiration := time.Now().Add(time.Hour * 24 * 365 * 100) + c.SetCookie(&http.Cookie{ Name: "sessionToken", Value: session.ID.String(), SameSite: http.SameSiteStrictMode, + Expires: expiration, Path: "/", }) return c.JSON(http.StatusOK, map[string]string{"message": "Signup successful!"}) - - // http.SetCookie(c.Writer, &http.Cookie{ - // Name: "sessionToken", - // Value: sessionID, - // Path: "/", - // }) - - // c.JSON(http.StatusOK, gin.H{"message": "Signup successful"}) } func GenerateSessionToken(db *bun.DB, userId uuid.UUID) (*models.Session, error) { @@ -136,3 +129,12 @@ func GenerateSessionToken(db *bun.DB, userId uuid.UUID) (*models.Session, error) return session, err } + +func GetUser(c echo.Context) error { + user := c.Get("user") + if user == nil { + return c.JSON(http.StatusNotFound, map[string]string{"message": "User not found"}) + } + + return c.JSON(http.StatusOK, user.(*models.User)) +} diff --git a/routes/files.go b/routes/files.go new file mode 100644 index 0000000..8c1272f --- /dev/null +++ b/routes/files.go @@ -0,0 +1,194 @@ +package routes + +import ( + "filething/models" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/labstack/echo/v4" +) + +type UploadResponse struct { + Usage int64 `json:"usage"` + File File `json:"file"` +} + +func UploadFile(c echo.Context) error { + user := c.Get("user").(*models.User) + + fullPath := strings.Trim(c.Param("*"), "/") + basePath := fmt.Sprintf("%s/%s/%s/", os.Getenv("STORAGE_PATH"), user.ID, fullPath) + + currentUsage, err := calculateStorageUsage(basePath) + if err != nil { + fmt.Println(err) + return err + } + + reader, err := c.Request().MultipartReader() + if err != nil { + fmt.Println(err) + return err + } + + part, err := reader.NextPart() + if err != nil { + fmt.Println(err) + return err + } + + filepath := filepath.Join(basePath, part.FileName()) + + if _, err = os.Stat(filepath); err == nil { + return c.JSON(http.StatusConflict, map[string]string{"message": "File with that name already exists"}) + } + + dst, err := os.Create(filepath) + if err != nil { + fmt.Println(err) + return err + } + defer dst.Close() + + // Read the file manually because otherwise we are limited by the arbitrarily small size of /tmp + buffer := make([]byte, 4096) + totalSize := int64(0) + + for { + n, readErr := part.Read(buffer) + + if readErr != nil && readErr == io.ErrUnexpectedEOF { + dst.Close() + os.Remove(filepath) + return c.JSON(http.StatusRequestTimeout, map[string]string{"message": "Upload canceled"}) + } + + if readErr != nil && readErr != io.EOF { + fmt.Println(err) + return readErr + } + + totalSize += int64(n) + + if currentUsage+totalSize > user.Plan.MaxStorage { + dst.Close() + os.Remove(filepath) + return c.JSON(http.StatusInsufficientStorage, map[string]string{"message": "Insufficient storage space"}) + } + + if _, err := dst.Write(buffer[:n]); err != nil { + fmt.Println(err) + return err + } + + if n == 0 || readErr == io.EOF { + entry, err := os.Stat(filepath) + + if err != nil { + fmt.Println(err) + return err + } + + uploadFile := &UploadResponse{ + Usage: currentUsage + totalSize, + File: File{ + Name: entry.Name(), + IsDir: entry.IsDir(), + Size: entry.Size(), + LastModified: entry.ModTime().Format("1/2/2006"), + }, + } + + return c.JSON(http.StatusOK, uploadFile) + } + } +} + +func calculateStorageUsage(basePath string) (int64, error) { + var totalSize int64 + + // Read the directory + entries, err := os.ReadDir(basePath) + if err != nil { + return 0, err + } + + // Iterate over directory entries + for _, entry := range entries { + if entry.IsDir() { + // Recursively calculate size of directories + dirPath := filepath.Join(basePath, entry.Name()) + dirSize, err := calculateStorageUsage(dirPath) + if err != nil { + return 0, err + } + totalSize += dirSize + } else { + // Calculate size of file + _ = filepath.Join(basePath, entry.Name()) + fileInfo, err := entry.Info() + if err != nil { + return 0, err + } + totalSize += fileInfo.Size() + } + } + + return totalSize, nil +} + +type File struct { + Name string `json:"name"` + IsDir bool `json:"is_dir"` + Size int64 `json:"size"` + LastModified string `json:"last_modified"` +} + +func GetFiles(c echo.Context) error { + user := c.Get("user").(*models.User) + + fullPath := strings.Trim(c.Param("*"), "/") + basePath := fmt.Sprintf("%s/%s/%s/", os.Getenv("STORAGE_PATH"), user.ID, fullPath) + + f, err := os.Open(basePath) + if err != nil { + fmt.Println(err) + return err + } + + files, err := f.Readdir(0) + if err != nil { + fmt.Println(err) + return err + } + + jsonFiles := make([]File, 0) + + for _, f := range files { + jsonFiles = append(jsonFiles, File{ + Name: f.Name(), + IsDir: f.IsDir(), + Size: f.Size(), + LastModified: f.ModTime().Format("1/2/2006"), + }) + } + + return c.JSON(http.StatusOK, jsonFiles) +} + +func GetUsage(c echo.Context) error { + user := c.Get("user").(*models.User) + + fullPath := strings.Trim(c.Param("*"), "/") + basePath := fmt.Sprintf("%s/%s/%s/", os.Getenv("STORAGE_PATH"), user.ID, fullPath) + storageUsage, err := calculateStorageUsage(basePath) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, map[string]int64{"usage": storageUsage}) +} diff --git a/ui/assets/css/main.css b/ui/assets/css/main.css index ce51e27..9d8d55b 100644 --- a/ui/assets/css/main.css +++ b/ui/assets/css/main.css @@ -4,37 +4,47 @@ @layer base { :root { - --color-base: 250 244 237; - --color-surface: 255 250 243; - --color-overlay: 242 233 225; - --color-muted: 152 147 165; - --color-subtle: 121 117 147; - --color-text: 87 82 121; - --highlight-low: 33 32 46; - --highlight-med: 64 61 82; - --highlight-high: 82 79 103; + --color-base: 239 237 242; + --color-surface: 247 246 249; + --color-overlay: 242 240 245; + --color-muted: 146 142 175; + --color-subtle: 139 136 160; + --color-text: 14 13 17; + --highlight-low: 11 18 22; + --highlight-med: 32 37 38; + --highlight-high: 49 55 58; --color-foam: 86 148 159; - --color-love: 180 99 122; + --color-love: 220 100 130; --color-pine: 40 105 131; + + --nav-height: 48px; } } .dark { - --color-base: 25 23 36; - --color-surface: 31 29 46; - --color-overlay: 38 35 58; - --color-muted: 110 106 134; - --color-subtle: 144 140 170; - --color-text: 224 222 244; + --color-base: 14 13 17; + --color-surface: 21 18 28; + --color-overlay: 30 26 40; + --color-muted: 99 92 117; + --color-subtle: 152 146 171; + --color-text: 247 246 249; --highlight-low: 244 237 232; --highlight-med: 223 218 217; --highlight-high: 206 202 205; --color-foam: 156 207 216; --color-love: 235 111 146; --color-pine: 49 116 143; + + color-scheme: dark; +} + +*, ::before, ::after { + border-color: rgb(var(--color-muted) / 0.2); + @apply ease-[cubic-bezier(0.25,_1,_0.5,_1)]; } body { + background-color: rgb(var(--color-base)); color: rgb(var(--color-text)); } diff --git a/ui/bun.lockb b/ui/bun.lockb index 48a2d612b5a3d6dd865bae392b40eb2685cbd895..c5debe2d42e198719536203f5f5f89a5e7d39dae 100755 GIT binary patch delta 48737 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%0AT9xJp6PE`3Df2y^ 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 +const props = defineProps({ + path: { + type: String, + required: true + } +}); + +const crumbs = computed(() => { + const paths = props.path.split("/").filter(x => !!x); + return paths.map((crumb, index) => { + return { + name: crumb, + link: "/" + paths.slice(0, index + 1).join("/") + }; + }); +}); + + + \ No newline at end of file diff --git a/ui/components/FileNav.vue b/ui/components/FileNav.vue new file mode 100644 index 0000000..d4e3747 --- /dev/null +++ b/ui/components/FileNav.vue @@ -0,0 +1,116 @@ + + + \ No newline at end of file diff --git a/ui/components/Footer.vue b/ui/components/Footer.vue new file mode 100644 index 0000000..9a39118 --- /dev/null +++ b/ui/components/Footer.vue @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/ui/components/Input.vue b/ui/components/Input.vue index 446eb06..b47c693 100644 --- a/ui/components/Input.vue +++ b/ui/components/Input.vue @@ -12,6 +12,6 @@ function updateValue(value) { \ No newline at end of file diff --git a/ui/components/Nav.vue b/ui/components/Nav.vue index ba53413..aff9e0f 100644 --- a/ui/components/Nav.vue +++ b/ui/components/Nav.vue @@ -1,2 +1,69 @@ + + \ No newline at end of file diff --git a/ui/components/UploadPane.vue b/ui/components/UploadPane.vue new file mode 100644 index 0000000..470eda9 --- /dev/null +++ b/ui/components/UploadPane.vue @@ -0,0 +1,239 @@ + + + \ No newline at end of file diff --git a/ui/composables/useUser.ts b/ui/composables/useUser.ts new file mode 100644 index 0000000..820079c --- /dev/null +++ b/ui/composables/useUser.ts @@ -0,0 +1,49 @@ +import type { User } from '~/types/user' +import { useFetch } from '#app' + +export const useUser = () => { + // Global state for storing the user + const user = useState('user', () => { return { fetched: false, user: {} } }) + + // Fetch the user only if it's uninitialized (i.e., null) + const getUser = async () => { + if (!user.value.fetched && import.meta.client) { + await fetchUser() + } + + return user.value.user + } + + const fetchUser = async () => { + try { + const { data, error } = await useFetch('/api/user') + user.value.fetched = true + + if (error.value || !data.value) { + throw new Error('Failed to fetch user') + } + + user.value.user = data.value + } catch (e) { + console.error(e.message) + user.value.user = {} + } + } + + // Manually set the user (e.g., after login/signup) + const setUser = (userData: User) => { + user.value.user = userData + } + + // Clear the user data (e.g., on logout) + const resetUser = () => { + user.value.user = {} + } + + return { + getUser, + setUser, + resetUser, + fetchUser, + } +} \ No newline at end of file diff --git a/ui/middleware/auth.ts b/ui/middleware/auth.ts new file mode 100644 index 0000000..1e4d5ab --- /dev/null +++ b/ui/middleware/auth.ts @@ -0,0 +1,15 @@ +import { useUser } from '~/composables/useUser' + +// We have server side things that does effectively this, but that wont stop SPA navigation +export default defineNuxtRouteMiddleware(async (to, from) => { + if (import.meta.server) { + return + } + + const { getUser } = useUser() + const user = await getUser() + + if (!user.id) { + return navigateTo('/login') + } +}) diff --git a/ui/middleware/unauth.ts b/ui/middleware/unauth.ts new file mode 100644 index 0000000..ae7f86a --- /dev/null +++ b/ui/middleware/unauth.ts @@ -0,0 +1,15 @@ +import { useUser } from '~/composables/useUser' + +// We have server side things that does effectively this, but that wont stop SPA navigation +export default defineNuxtRouteMiddleware(async (to, from) => { + if (import.meta.server) { + return + } + + const { getUser } = useUser() + const user = await getUser() + + if (user.id) { + return navigateTo('/home') + } +}) diff --git a/ui/pages/home.vue b/ui/pages/home.vue deleted file mode 100644 index 2eea24d..0000000 --- a/ui/pages/home.vue +++ /dev/null @@ -1,12 +0,0 @@ - - - - - \ No newline at end of file diff --git a/ui/pages/home/[...name].vue b/ui/pages/home/[...name].vue new file mode 100644 index 0000000..84245b8 --- /dev/null +++ b/ui/pages/home/[...name].vue @@ -0,0 +1,266 @@ + + + diff --git a/ui/pages/index.vue b/ui/pages/index.vue index 43bc3ee..3a98d1f 100644 --- a/ui/pages/index.vue +++ b/ui/pages/index.vue @@ -1,10 +1,193 @@ + + - \ No newline at end of file diff --git a/ui/pages/login.vue b/ui/pages/login.vue index a9afe4b..c8d69ef 100644 --- a/ui/pages/login.vue +++ b/ui/pages/login.vue @@ -1,9 +1,10 @@ -