Implement AsciiDoc and TipTap conversion logic, including new Admonition node and CodeBlock extension; add DiffViewer and HistoryPanel components for document version comparison; introduce password hashing utility with SHA-256.
This commit is contained in:
@@ -64,6 +64,11 @@ func (s *Server) initRuntime(cfg *config.Config) {
|
||||
}
|
||||
|
||||
var d *db.DB
|
||||
if dir := filepath.Dir(cfg.DBPath); dir != "." && dir != "" {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
log.Printf("[db] failed to create directory %s: %v", dir, err)
|
||||
}
|
||||
}
|
||||
if database, err := db.New(cfg.DBPath); err != nil {
|
||||
log.Printf("[db] failed to open at %s: %v", cfg.DBPath, err)
|
||||
} else {
|
||||
@@ -127,8 +132,11 @@ func (s *Server) handleGraphQL(w http.ResponseWriter, r *http.Request) {
|
||||
s.mu.RLock()
|
||||
cfg := s.cfg
|
||||
store := s.store
|
||||
database := s.database
|
||||
s.mu.RUnlock()
|
||||
|
||||
needsSetup := cfg == nil || database == nil || !database.HasUsers()
|
||||
|
||||
q := req.Query
|
||||
|
||||
switch {
|
||||
@@ -137,7 +145,7 @@ func (s *Server) handleGraphQL(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleSetup(w, req)
|
||||
|
||||
case strings.Contains(q, "systemStatus"):
|
||||
if cfg == nil {
|
||||
if needsSetup {
|
||||
writeJSON(w, `{"data":{"systemStatus":"REQUIRE_SETUP"}}`)
|
||||
} else {
|
||||
writeJSON(w, `{"data":{"systemStatus":"OK"}}`)
|
||||
@@ -155,7 +163,7 @@ func (s *Server) handleGraphQL(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// ── All others require initialised config ─────────────────────────────────
|
||||
default:
|
||||
if cfg == nil {
|
||||
if needsSetup {
|
||||
writeGQLError(w, "REQUIRE_SETUP")
|
||||
return
|
||||
}
|
||||
@@ -201,17 +209,23 @@ func (s *Server) dispatchAuthenticated(
|
||||
case strings.Contains(q, "config"):
|
||||
s.handleConfig(w, sess)
|
||||
|
||||
case strings.Contains(q, "history"):
|
||||
s.handleHistory(w, req, sess)
|
||||
case strings.Contains(q, "repoStatus"):
|
||||
s.handleRepoStatus(w, sess)
|
||||
|
||||
case strings.Contains(q, "diff"):
|
||||
s.handleDiff(w, req, sess)
|
||||
case strings.Contains(q, "initCommit"):
|
||||
s.handleInitCommit(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "documentAtCommit"):
|
||||
s.handleDocumentAtCommit(w, req, sess)
|
||||
case strings.Contains(q, "history"):
|
||||
s.handleHistory(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "documents") || strings.Contains(q, "document"):
|
||||
s.handleDocuments(w, req, sess, store)
|
||||
case strings.Contains(q, "diff"):
|
||||
s.handleDiff(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "documentAtCommit"):
|
||||
s.handleDocumentAtCommit(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "documents") || strings.Contains(q, "document"):
|
||||
s.handleDocuments(w, req, sess, store)
|
||||
|
||||
default:
|
||||
writeJSON(w, `{"data":{"systemStatus":"OK"}}`)
|
||||
@@ -826,6 +840,63 @@ func (s *Server) handleConfig(w http.ResponseWriter, sess *auth.Session) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleRepoStatus(w http.ResponseWriter, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
repo := s.gitRepo
|
||||
s.mu.RUnlock()
|
||||
|
||||
hasUncommitted := repo != nil && repo.HasUncommitted()
|
||||
writeJSONObj(w, map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"repoStatus": map[string]interface{}{
|
||||
"hasUncommitted": hasUncommitted,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleInitCommit(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
message, _ := req.Variables["message"].(string)
|
||||
if message == "" {
|
||||
message = "Initial commit"
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
repo := s.gitRepo
|
||||
s.mu.RUnlock()
|
||||
|
||||
if repo == nil {
|
||||
writeGQLError(w, "storage not initialised")
|
||||
return
|
||||
}
|
||||
|
||||
if !repo.HasUncommitted() {
|
||||
// Nothing to commit — return success without error.
|
||||
writeJSON(w, `{"data":{"initCommit":true}}`)
|
||||
return
|
||||
}
|
||||
|
||||
email := sess.Username + "@archivum"
|
||||
if err := repo.CommitAll(message, sess.Username, email); err != nil {
|
||||
log.Printf("[git] initCommit failed for %s: %v", sess.Username, err)
|
||||
writeGQLError(w, fmt.Sprintf("commit failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[git] initCommit by %s: %q", sess.Username, message)
|
||||
writeJSON(w, `{"data":{"initCommit":true}}`)
|
||||
}
|
||||
|
||||
func (s *Server) handleUpdateStoragePath(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
log.Printf("[unauth] session is nil")
|
||||
@@ -886,7 +957,7 @@ func (s *Server) handleSetup(w http.ResponseWriter, req gqlRequest) {
|
||||
|
||||
cfg := &config.Config{
|
||||
StoragePath: storagePath,
|
||||
DBPath: "/data/db/archivum.db",
|
||||
DBPath: resolveDBPath(s.configPath),
|
||||
JWTSecret: jwtSecret,
|
||||
ListenAddr: ":4000",
|
||||
}
|
||||
@@ -905,19 +976,8 @@ func (s *Server) handleSetup(w http.ResponseWriter, req gqlRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dirOf(s.configPath), 0755); err != nil {
|
||||
writeGQLError(w, fmt.Sprintf("could not create config directory: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := config.Save(s.configPath, cfg); err != nil {
|
||||
writeGQLError(w, fmt.Sprintf("failed to save config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[setup] config written to %s", s.configPath)
|
||||
|
||||
// Open database and create admin user.
|
||||
// Open database and create admin user BEFORE saving config,
|
||||
// so a failed DB creation does not leave a broken config.json on disk.
|
||||
if err := os.MkdirAll(dirOf(cfg.DBPath), 0755); err != nil {
|
||||
writeGQLError(w, fmt.Sprintf("could not create db directory: %v", err))
|
||||
return
|
||||
@@ -942,6 +1002,18 @@ func (s *Server) handleSetup(w http.ResponseWriter, req gqlRequest) {
|
||||
|
||||
log.Printf("[setup] admin user %q created", adminUser)
|
||||
|
||||
if err := os.MkdirAll(dirOf(s.configPath), 0755); err != nil {
|
||||
writeGQLError(w, fmt.Sprintf("could not create config directory: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := config.Save(s.configPath, cfg); err != nil {
|
||||
writeGQLError(w, fmt.Sprintf("failed to save config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[setup] config written to %s", s.configPath)
|
||||
|
||||
store, err := storage.New(storagePath)
|
||||
if err != nil {
|
||||
log.Printf("[setup] failed to open storage at %s: %v", storagePath, err)
|
||||
@@ -1144,6 +1216,17 @@ func dirOf(path string) string {
|
||||
return path[:idx]
|
||||
}
|
||||
|
||||
// resolveDBPath returns the best DB path given the config file location.
|
||||
// Prefers /data/db/archivum.db (Docker volume) if /data is writable;
|
||||
// otherwise places the DB next to the config file.
|
||||
func resolveDBPath(configPath string) string {
|
||||
const dockerDB = "/data/db/archivum.db"
|
||||
if err := os.MkdirAll("/data/db", 0755); err == nil {
|
||||
return dockerDB
|
||||
}
|
||||
return filepath.Join(dirOf(configPath), "archivum.db")
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (s *Server) handleHistory(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
@@ -1174,6 +1257,8 @@ func (s *Server) handleHistory(w http.ResponseWriter, req gqlRequest, sess *auth
|
||||
"email": entry.Email,
|
||||
"date": entry.Date,
|
||||
"subject": entry.Subject,
|
||||
"added": entry.Added,
|
||||
"removed": entry.Removed,
|
||||
})
|
||||
}
|
||||
writeJSONObj(w, map[string]interface{}{ "data": map[string]interface{}{ "history": out } })
|
||||
|
||||
Reference in New Issue
Block a user