+
Skip to content

Feat/last opened file #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions backend/internal/api/file_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,19 +120,19 @@ func DeleteFile(fs *filesystem.FileSystem) http.HandlerFunc {

func GetLastOpenedFile(db *db.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userID, _, err := getUserAndWorkspaceIDs(r)
_, workspaceID, err := getUserAndWorkspaceIDs(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

user, err := db.GetUserByID(userID)
filePath, err := db.GetLastOpenedFile(workspaceID)
if err != nil {
http.Error(w, "Failed to get user", http.StatusInternalServerError)
http.Error(w, "Failed to get last opened file", http.StatusInternalServerError)
return
}

respondJSON(w, map[string]string{"lastOpenedFile": user.LastOpenedFilePath})
respondJSON(w, map[string]string{"lastOpenedFilePath": filePath})
}
}

Expand All @@ -153,14 +153,15 @@ func UpdateLastOpenedFile(db *db.DB, fs *filesystem.FileSystem) http.HandlerFunc
return
}

// Validate that the file path is valid within the workspace
_, err = fs.ValidatePath(userID, workspaceID, requestBody.FilePath)
if err != nil {
http.Error(w, "Invalid file path", http.StatusBadRequest)
return
// Validate the file path exists in the workspace
if requestBody.FilePath != "" {
if _, err := fs.ValidatePath(userID, workspaceID, requestBody.FilePath); err != nil {
http.Error(w, "Invalid file path", http.StatusBadRequest)
return
}
}

if err := db.UpdateLastOpenedFile(userID, requestBody.FilePath); err != nil {
if err := db.UpdateLastOpenedFile(workspaceID, requestBody.FilePath); err != nil {
http.Error(w, "Failed to update last opened file", http.StatusInternalServerError)
return
}
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/db/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ var migrations = []Migration{
password_hash TEXT NOT NULL,
role TEXT NOT NULL CHECK(role IN ('admin', 'editor', 'viewer')),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_workspace_id INTEGER,
last_opened_file_path TEXT
last_workspace_id INTEGER
);

-- Create workspaces table with integrated settings
Expand All @@ -32,6 +31,7 @@ var migrations = []Migration{
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_opened_file_path TEXT,
-- Settings fields
theme TEXT NOT NULL DEFAULT 'light' CHECK(theme IN ('light', 'dark')),
auto_save BOOLEAN NOT NULL DEFAULT 0,
Expand Down
44 changes: 15 additions & 29 deletions backend/internal/db/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,13 @@ func (db *DB) createWorkspaceTx(tx *sql.Tx, workspace *models.Workspace) error {
func (db *DB) GetUserByID(id int) (*models.User, error) {
user := &models.User{}
err := db.QueryRow(`
SELECT
u.id, u.email, u.display_name, u.role, u.created_at,
u.last_workspace_id, u.last_opened_file_path,
COALESCE(w.id, 0) as workspace_id
FROM users u
LEFT JOIN workspaces w ON w.id = u.last_workspace_id
WHERE u.id = ?`, id).
SELECT
id, email, display_name, role, created_at,
last_workspace_id
FROM users
WHERE id = ?`, id).
Scan(&user.ID, &user.Email, &user.DisplayName, &user.Role, &user.CreatedAt,
&user.LastWorkspaceID, &user.LastOpenedFilePath, &user.LastWorkspaceID)
&user.LastWorkspaceID)
if err != nil {
return nil, err
}
Expand All @@ -98,34 +96,27 @@ func (db *DB) GetUserByID(id int) (*models.User, error) {

func (db *DB) GetUserByEmail(email string) (*models.User, error) {
user := &models.User{}
var lastOpenedFilePath sql.NullString
err := db.QueryRow(`
SELECT
u.id, u.email, u.display_name, u.password_hash, u.role, u.created_at,
u.last_workspace_id, u.last_opened_file_path,
COALESCE(w.id, 0) as workspace_id
FROM users u
LEFT JOIN workspaces w ON w.id = u.last_workspace_id
WHERE u.email = ?`, email).
SELECT
id, email, display_name, password_hash, role, created_at,
last_workspace_id
FROM users
WHERE email = ?`, email).
Scan(&user.ID, &user.Email, &user.DisplayName, &user.PasswordHash, &user.Role, &user.CreatedAt,
&user.LastWorkspaceID, &lastOpenedFilePath, &user.LastWorkspaceID)
&user.LastWorkspaceID)
if err != nil {
return nil, err
}
if lastOpenedFilePath.Valid {
user.LastOpenedFilePath = lastOpenedFilePath.String
} else {
user.LastOpenedFilePath = ""
}

return user, nil
}

func (db *DB) UpdateUser(user *models.User) error {
_, err := db.Exec(`
UPDATE users
SET email = ?, display_name = ?, role = ?, last_workspace_id = ?, last_opened_file_path = ?
SET email = ?, display_name = ?, role = ?, last_workspace_id = ?
WHERE id = ?`,
user.Email, user.DisplayName, user.Role, user.LastWorkspaceID, user.LastOpenedFilePath, user.ID)
user.Email, user.DisplayName, user.Role, user.LastWorkspaceID, user.ID)
return err
}

Expand All @@ -134,11 +125,6 @@ func (db *DB) UpdateLastWorkspace(userID, workspaceID int) error {
return err
}

func (db *DB) UpdateLastOpenedFile(userID int, filePath string) error {
_, err := db.Exec("UPDATE users SET last_opened_file_path = ? WHERE id = ?", filePath, userID)
return err
}

func (db *DB) DeleteUser(id int) error {
tx, err := db.Begin()
if err != nil {
Expand Down
17 changes: 17 additions & 0 deletions backend/internal/db/workspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,20 @@ func (db *DB) UpdateLastWorkspaceTx(tx *sql.Tx, userID, workspaceID int) error {
_, err := tx.Exec("UPDATE users SET last_workspace_id = ? WHERE id = ?", workspaceID, userID)
return err
}

func (db *DB) UpdateLastOpenedFile(workspaceID int, filePath string) error {
_, err := db.Exec("UPDATE workspaces SET last_opened_file_path = ? WHERE id = ?", filePath, workspaceID)
return err
}

func (db *DB) GetLastOpenedFile(workspaceID int) (string, error) {
var filePath sql.NullString
err := db.QueryRow("SELECT last_opened_file_path FROM workspaces WHERE id = ?", workspaceID).Scan(&filePath)
if err != nil {
return "", err
}
if !filePath.Valid {
return "", nil
}
return filePath.String, nil
}
15 changes: 7 additions & 8 deletions backend/internal/models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ const (
)

type User struct {
ID int `json:"id" validate:"required,min=1"`
Email string `json:"email" validate:"required,email"`
DisplayName string `json:"displayName"`
PasswordHash string `json:"-"`
Role UserRole `json:"role" validate:"required,oneof=admin editor viewer"`
CreatedAt time.Time `json:"createdAt"`
LastWorkspaceID int `json:"lastWorkspaceId"`
LastOpenedFilePath string `json:"lastOpenedFilePath"`
ID int `json:"id" validate:"required,min=1"`
Email string `json:"email" validate:"required,email"`
DisplayName string `json:"displayName"`
PasswordHash string `json:"-"`
Role UserRole `json:"role" validate:"required,oneof=admin editor viewer"`
CreatedAt time.Time `json:"createdAt"`
LastWorkspaceID int `json:"lastWorkspaceId"`
}

func (u *User) Validate() error {
Expand Down
9 changes: 5 additions & 4 deletions backend/internal/models/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import (
)

type Workspace struct {
ID int `json:"id" validate:"required,min=1"`
UserID int `json:"userId" validate:"required,min=1"`
Name string `json:"name" validate:"required"`
CreatedAt time.Time `json:"createdAt"`
ID int `json:"id" validate:"required,min=1"`
UserID int `json:"userId" validate:"required,min=1"`
Name string `json:"name" validate:"required"`
CreatedAt time.Time `json:"createdAt"`
LastOpenedFilePath string `json:"lastOpenedFilePath"`

// Integrated settings
Theme string `json:"theme" validate:"oneof=light dark"`
Expand Down
3 changes: 2 additions & 1 deletion backend/internal/user/user.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package user

import (
"database/sql"
"fmt"
"log"

Expand Down Expand Up @@ -28,7 +29,7 @@ func (s *UserService) SetupAdminUser(adminEmail, adminPassword string) (*models.
adminUser, err := s.DB.GetUserByEmail(adminEmail)
if adminUser != nil {
return adminUser, nil // Admin user already exists
} else if err != nil {
} else if err != sql.ErrNoRows {
return nil, err
}

Expand Down
35 changes: 27 additions & 8 deletions frontend/src/hooks/useFileNavigation.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { useState, useCallback, useEffect } from 'react'; // Added useEffect
import { useState, useCallback, useEffect } from 'react';
import { notifications } from '@mantine/notifications';
import { lookupFileByName } from '../services/api';
import { DEFAULT_FILE } from '../utils/constants';
import { useWorkspace } from '../contexts/WorkspaceContext';
import { useLastOpenedFile } from './useLastOpenedFile';

export const useFileNavigation = () => {
const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path);
const [isNewFile, setIsNewFile] = useState(true);
const { currentWorkspace } = useWorkspace();
const { loadLastOpenedFile, saveLastOpenedFile } = useLastOpenedFile();

const handleFileSelect = useCallback((filePath) => {
setSelectedFile(filePath || DEFAULT_FILE.path);
setIsNewFile(filePath ? false : true);
}, []);
const handleFileSelect = useCallback(
async (filePath) => {
const newPath = filePath || DEFAULT_FILE.path;
setSelectedFile(newPath);
setIsNewFile(!filePath);

if (filePath) {
await saveLastOpenedFile(filePath);
}
},
[saveLastOpenedFile]
);

const handleLinkClick = useCallback(
async (filename) => {
Expand Down Expand Up @@ -41,10 +51,19 @@ export const useFileNavigation = () => {
[currentWorkspace, handleFileSelect]
);

// Reset to default file when workspace changes
// Load last opened file when workspace changes
useEffect(() => {
handleFileSelect(null);
}, [currentWorkspace, handleFileSelect]);
const initializeFile = async () => {
const lastFile = await loadLastOpenedFile();
if (lastFile) {
handleFileSelect(lastFile);
} else {
handleFileSelect(null);
}
};

initializeFile();
}, [currentWorkspace, loadLastOpenedFile, handleFileSelect]);

return { handleLinkClick, selectedFile, isNewFile, handleFileSelect };
};
37 changes: 37 additions & 0 deletions frontend/src/hooks/useLastOpenedFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useCallback } from 'react';
import { getLastOpenedFile, updateLastOpenedFile } from '../services/api';
import { useWorkspace } from '../contexts/WorkspaceContext';

export const useLastOpenedFile = () => {
const { currentWorkspace } = useWorkspace();

const loadLastOpenedFile = useCallback(async () => {
if (!currentWorkspace) return null;

try {
const response = await getLastOpenedFile(currentWorkspace.id);
return response.lastOpenedFilePath || null;
} catch (error) {
console.error('Failed to load last opened file:', error);
return null;
}
}, [currentWorkspace]);

const saveLastOpenedFile = useCallback(
async (filePath) => {
if (!currentWorkspace) return;

try {
await updateLastOpenedFile(currentWorkspace.id, filePath);
} catch (error) {
console.error('Failed to save last opened file:', error);
}
},
[currentWorkspace]
);

return {
loadLastOpenedFile,
saveLastOpenedFile,
};
};
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载