heroagent/aiprompts/instructions/instructions1.md
2025-04-23 04:18:28 +02:00

11 KiB

create a golang project

there will be multiple

  • modules

    • one is for installers
    • one is for a fiber web server with a web ui, swagger UI and opeapi rest interface (v3.1.0 swagger)
    • a generic redis server
  • on the fiber webserver create multiple endpoints nicely structures as separate directories underneith the module

    • executor (for executing commands, results in jobs)
    • package manager (on basis of apt, brew, scoop)
    • create an openapi interface for each of those v3.1.0
    • integrate in generic way the goswagger interface so people can use the rest interface from web
  • create a main server which connects to all the modules

code for the redis server

package main

import (
	"fmt"
	"log"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/tidwall/redcon"
)

// entry represents a stored value. For strings, value is stored as a string.
// For hashes, value is stored as a map[string]string.
type entry struct {
	value      interface{}
	expiration time.Time // zero means no expiration
}

// Server holds the in-memory datastore and provides thread-safe access.
type Server struct {
	mu   sync.RWMutex
	data map[string]*entry
}

// NewServer creates a new server instance and starts a cleanup goroutine.
func NewServer() *Server {
	s := &Server{
		data: make(map[string]*entry),
	}
	go s.cleanupExpiredKeys()
	return s
}

// cleanupExpiredKeys periodically removes expired keys.
func (s *Server) cleanupExpiredKeys() {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()
	for range ticker.C {
		now := time.Now()
		s.mu.Lock()
		for k, ent := range s.data {
			if !ent.expiration.IsZero() && now.After(ent.expiration) {
				delete(s.data, k)
			}
		}
		s.mu.Unlock()
	}
}

// set stores a key with a value and an optional expiration duration.
func (s *Server) set(key string, value interface{}, duration time.Duration) {
	s.mu.Lock()
	defer s.mu.Unlock()
	var exp time.Time
	if duration > 0 {
		exp = time.Now().Add(duration)
	}
	s.data[key] = &entry{
		value:      value,
		expiration: exp,
	}
}

// get retrieves the value for a key if it exists and is not expired.
func (s *Server) get(key string) (interface{}, bool) {
	s.mu.RLock()
	ent, ok := s.data[key]
	s.mu.RUnlock()
	if !ok {
		return nil, false
	}
	if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
		// Key has expired; remove it.
		s.mu.Lock()
		delete(s.data, key)
		s.mu.Unlock()
		return nil, false
	}
	return ent.value, true
}

// del deletes a key and returns 1 if the key was present.
func (s *Server) del(key string) int {
	s.mu.Lock()
	defer s.mu.Unlock()
	if _, ok := s.data[key]; ok {
		delete(s.data, key)
		return 1
	}
	return 0
}

// keys returns all keys matching the given pattern.
// For simplicity, only "*" is fully supported.
func (s *Server) keys(pattern string) []string {
	s.mu.RLock()
	defer s.mu.RUnlock()
	var result []string
	// Simple pattern matching: if pattern is "*", return all nonexpired keys.
	if pattern == "*" {
		for k, ent := range s.data {
			if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
				continue
			}
			result = append(result, k)
		}
	} else {
		// For any other pattern, do a simple substring match.
		for k, ent := range s.data {
			if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
				continue
			}
			if strings.Contains(k, pattern) {
				result = append(result, k)
			}
		}
	}
	return result
}

// getHash retrieves the hash map stored at key.
func (s *Server) getHash(key string) (map[string]string, bool) {
	v, ok := s.get(key)
	if !ok {
		return nil, false
	}
	hash, ok := v.(map[string]string)
	return hash, ok
}

// hset sets a field in the hash stored at key. It returns 1 if the field is new.
func (s *Server) hset(key, field, value string) int {
	s.mu.Lock()
	defer s.mu.Unlock()
	var hash map[string]string
	ent, exists := s.data[key]
	if exists {
		if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
			// expired; recreate a new hash.
			hash = make(map[string]string)
			s.data[key] = &entry{value: hash}
		} else {
			var ok bool
			hash, ok = ent.value.(map[string]string)
			if !ok {
				// Overwrite if the key holds a non-hash value.
				hash = make(map[string]string)
				s.data[key] = &entry{value: hash}
			}
		}
	} else {
		hash = make(map[string]string)
		s.data[key] = &entry{value: hash}
	}
	_, fieldExists := hash[field]
	hash[field] = value
	if fieldExists {
		return 0
	}
	return 1
}

// hget retrieves the value of a field in the hash stored at key.
func (s *Server) hget(key, field string) (string, bool) {
	hash, ok := s.getHash(key)
	if !ok {
		return "", false
	}
	val, exists := hash[field]
	return val, exists
}

// hdel deletes one or more fields from the hash stored at key.
// Returns the number of fields that were removed.
func (s *Server) hdel(key string, fields []string) int {
	hash, ok := s.getHash(key)
	if !ok {
		return 0
	}
	count := 0
	for _, field := range fields {
		if _, exists := hash[field]; exists {
			delete(hash, field)
			count++
		}
	}
	return count
}

// hkeys returns all field names in the hash stored at key.
func (s *Server) hkeys(key string) []string {
	hash, ok := s.getHash(key)
	if !ok {
		return nil
	}
	var keys []string
	for field := range hash {
		keys = append(keys, field)
	}
	return keys
}

// hlen returns the number of fields in the hash stored at key.
func (s *Server) hlen(key string) int {
	hash, ok := s.getHash(key)
	if !ok {
		return 0
	}
	return len(hash)
}

// incr increments the integer value stored at key by one.
// If the key does not exist, it is set to 0 before performing the operation.
func (s *Server) incr(key string) (int64, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	var current int64
	ent, exists := s.data[key]
	if exists {
		if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
			current = 0
		} else {
			switch v := ent.value.(type) {
			case string:
				var err error
				current, err = strconv.ParseInt(v, 10, 64)
				if err != nil {
					return 0, err
				}
			case int:
				current = int64(v)
			case int64:
				current = v
			default:
				return 0, fmt.Errorf("value is not an integer")
			}
		}
	}
	current++
	// Store the new value as a string.
	s.data[key] = &entry{
		value: strconv.FormatInt(current, 10),
	}
	return current, nil
}

func main() {
	server := NewServer()
	log.Println("Starting Redis-like server on :6379")
	err := redcon.ListenAndServe(":6379",
		func(conn redcon.Conn, cmd redcon.Command) {
			// Every command is expected to have at least one argument (the command name).
			if len(cmd.Args) == 0 {
				conn.WriteError("ERR empty command")
				return
			}
			command := strings.ToLower(string(cmd.Args[0]))
			switch command {
			case "ping":
				conn.WriteString("PONG")
			case "set":
				// Usage: SET key value [EX seconds]
				if len(cmd.Args) < 3 {
					conn.WriteError("ERR wrong number of arguments for 'set' command")
					return
				}
				key := string(cmd.Args[1])
				value := string(cmd.Args[2])
				duration := time.Duration(0)
				// Check for an expiration option (only EX is supported here).
				if len(cmd.Args) > 3 {
					if strings.ToLower(string(cmd.Args[3])) == "ex" && len(cmd.Args) > 4 {
						seconds, err := strconv.Atoi(string(cmd.Args[4]))
						if err != nil {
							conn.WriteError("ERR invalid expire time")
							return
						}
						duration = time.Duration(seconds) * time.Second
					}
				}
				server.set(key, value, duration)
				conn.WriteString("OK")
			case "get":
				if len(cmd.Args) < 2 {
					conn.WriteError("ERR wrong number of arguments for 'get' command")
					return
				}
				key := string(cmd.Args[1])
				v, ok := server.get(key)
				if !ok {
					conn.WriteNull()
					return
				}
				// Only string type is returned by GET.
				switch val := v.(type) {
				case string:
					conn.WriteBulkString(val)
				default:
					conn.WriteError("WRONGTYPE Operation against a key holding the wrong kind of value")
				}
			case "del":
				if len(cmd.Args) < 2 {
					conn.WriteError("ERR wrong number of arguments for 'del' command")
					return
				}
				count := 0
				for i := 1; i < len(cmd.Args); i++ {
					key := string(cmd.Args[i])
					count += server.del(key)
				}
				conn.WriteInt(count)
			case "keys":
				if len(cmd.Args) < 2 {
					conn.WriteError("ERR wrong number of arguments for 'keys' command")
					return
				}
				pattern := string(cmd.Args[1])
				keys := server.keys(pattern)
				res := make([][]byte, len(keys))
				for i, k := range keys {
					res[i] = []byte(k)
				}
				conn.WriteArray(res)
			case "hset":
				// Usage: HSET key field value
				if len(cmd.Args) < 4 {
					conn.WriteError("ERR wrong number of arguments for 'hset' command")
					return
				}
				key := string(cmd.Args[1])
				field := string(cmd.Args[2])
				value := string(cmd.Args[3])
				added := server.hset(key, field, value)
				conn.WriteInt(added)
			case "hget":
				// Usage: HGET key field
				if len(cmd.Args) < 3 {
					conn.WriteError("ERR wrong number of arguments for 'hget' command")
					return
				}
				key := string(cmd.Args[1])
				field := string(cmd.Args[2])
				v, ok := server.hget(key, field)
				if !ok {
					conn.WriteNull()
					return
				}
				conn.WriteBulkString(v)
			case "hdel":
				// Usage: HDEL key field [field ...]
				if len(cmd.Args) < 3 {
					conn.WriteError("ERR wrong number of arguments for 'hdel' command")
					return
				}
				key := string(cmd.Args[1])
				fields := make([]string, 0, len(cmd.Args)-2)
				for i := 2; i < len(cmd.Args); i++ {
					fields = append(fields, string(cmd.Args[i]))
				}
				removed := server.hdel(key, fields)
				conn.WriteInt(removed)
			case "hkeys":
				// Usage: HKEYS key
				if len(cmd.Args) < 2 {
					conn.WriteError("ERR wrong number of arguments for 'hkeys' command")
					return
				}
				key := string(cmd.Args[1])
				fields := server.hkeys(key)
				res := make([][]byte, len(fields))
				for i, field := range fields {
					res[i] = []byte(field)
				}
				conn.WriteArray(res)
			case "hlen":
				// Usage: HLEN key
				if len(cmd.Args) < 2 {
					conn.WriteError("ERR wrong number of arguments for 'hlen' command")
					return
				}
				key := string(cmd.Args[1])
				length := server.hlen(key)
				conn.WriteInt(length)
			case "incr":
				if len(cmd.Args) < 2 {
					conn.WriteError("ERR wrong number of arguments for 'incr' command")
					return
				}
				key := string(cmd.Args[1])
				newVal, err := server.incr(key)
				if err != nil {
					conn.WriteError("ERR " + err.Error())
					return
				}
				conn.WriteInt64(newVal)
			default:
				conn.WriteError("ERR unknown command '" + command + "'")
			}
		},
		// Accept connection: always allow.
		func(conn redcon.Conn) bool { return true },
		// On connection close.
		func(conn redcon.Conn, err error) {},
	)
	if err != nil {
		log.Fatal(err)
	}
}

test above code, test with a redis client it works