314 lines
8.0 KiB
Go
314 lines
8.0 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"crypto/rand"
|
||
|
"encoding/hex"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"log"
|
||
|
"mime/multipart"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"sync"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/joho/godotenv"
|
||
|
)
|
||
|
|
||
|
type App struct {
|
||
|
Name string `json:"name"`
|
||
|
Version string `json:"ver"`
|
||
|
ID string `json:"appid"`
|
||
|
Info string `json:"info"`
|
||
|
Developer string `json:"pub"`
|
||
|
Path string `json:"path"`
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
apps []App
|
||
|
appsFilePath = "apps.json"
|
||
|
uploadDir = "uploads"
|
||
|
bearerToken string
|
||
|
mutex sync.Mutex
|
||
|
)
|
||
|
|
||
|
func loadApps() error {
|
||
|
data, err := ioutil.ReadFile(appsFilePath)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return json.Unmarshal(data, &apps)
|
||
|
}
|
||
|
|
||
|
func saveApps() error {
|
||
|
data, err := json.MarshalIndent(apps, "", " ")
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return ioutil.WriteFile(appsFilePath, data, 0644)
|
||
|
}
|
||
|
|
||
|
func generateToken() (string, error) {
|
||
|
tokenBytes := make([]byte, 16)
|
||
|
if _, err := rand.Read(tokenBytes); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
return hex.EncodeToString(tokenBytes), nil
|
||
|
}
|
||
|
|
||
|
func loadOrGenerateToken() error {
|
||
|
if err := godotenv.Load(); err != nil {
|
||
|
log.Println("No .env file found. Generating a new token.")
|
||
|
}
|
||
|
bearerToken = os.Getenv("TOKEN")
|
||
|
if bearerToken == "" {
|
||
|
var err error
|
||
|
bearerToken, err = generateToken()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to generate token: %v", err)
|
||
|
}
|
||
|
err = saveTokenToEnv(bearerToken)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to save token to .env: %v", err)
|
||
|
}
|
||
|
log.Printf("Generated new token: %s\n", bearerToken)
|
||
|
}
|
||
|
port := os.Getenv("PORT")
|
||
|
if port == "" {
|
||
|
port = "8080"
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func saveTokenToEnv(token string) error {
|
||
|
return ioutil.WriteFile(".env", []byte("TOKEN="+token), 0644)
|
||
|
}
|
||
|
|
||
|
func listAppsHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
w.Header().Set("Content-Type", "application/json")
|
||
|
mutex.Lock()
|
||
|
defer mutex.Unlock()
|
||
|
json.NewEncoder(w).Encode(apps)
|
||
|
}
|
||
|
|
||
|
func saveUploadedFile(customPath, fileName string, file multipart.File) (string, error) {
|
||
|
appDir := filepath.Join(uploadDir, customPath)
|
||
|
if err := os.MkdirAll(appDir, 0755); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
filePath := filepath.Join(appDir, fileName)
|
||
|
dst, err := os.Create(filePath)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
defer dst.Close()
|
||
|
|
||
|
if _, err := io.Copy(dst, file); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
return fmt.Sprintf("/%s/%s/%s", uploadDir, customPath, fileName), nil
|
||
|
}
|
||
|
|
||
|
func uploadAppHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
token := r.Header.Get("Authorization")
|
||
|
if token != "Bearer "+bearerToken {
|
||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
||
|
http.Error(w, "Invalid form data", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
metadata := r.FormValue("metadata")
|
||
|
var newApp App
|
||
|
if err := json.Unmarshal([]byte(metadata), &newApp); err != nil {
|
||
|
http.Error(w, "Invalid metadata JSON", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
file, handler, err := r.FormFile("file")
|
||
|
if err != nil {
|
||
|
http.Error(w, "File upload error", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
defer file.Close()
|
||
|
|
||
|
customPath := r.FormValue("customPath")
|
||
|
if customPath == "" {
|
||
|
customPath = strings.ReplaceAll(newApp.Name, " ", "_")
|
||
|
}
|
||
|
|
||
|
filePath, err := saveUploadedFile(customPath, handler.Filename, file)
|
||
|
if err != nil {
|
||
|
http.Error(w, "Failed to save file", http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
newApp.Path = filePath
|
||
|
|
||
|
mutex.Lock()
|
||
|
apps = append(apps, newApp)
|
||
|
if err := saveApps(); err != nil {
|
||
|
mutex.Unlock()
|
||
|
http.Error(w, "Failed to save app data", http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
mutex.Unlock()
|
||
|
|
||
|
w.WriteHeader(http.StatusCreated)
|
||
|
json.NewEncoder(w).Encode(newApp)
|
||
|
}
|
||
|
|
||
|
func deleteAppHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
token := r.Header.Get("Authorization")
|
||
|
if token != "Bearer "+bearerToken {
|
||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
appID := r.URL.Query().Get("id")
|
||
|
if appID == "" {
|
||
|
http.Error(w, "App ID is required", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
mutex.Lock()
|
||
|
defer mutex.Unlock()
|
||
|
|
||
|
for i, app := range apps {
|
||
|
if app.ID == appID {
|
||
|
// Remove the app's file if it exists
|
||
|
if err := os.Remove(filepath.Join(".", app.Path)); err != nil {
|
||
|
log.Printf("Failed to delete file: %v\n", err)
|
||
|
}
|
||
|
|
||
|
// Remove the app from the list
|
||
|
apps = append(apps[:i], apps[i+1:]...)
|
||
|
if err := saveApps(); err != nil {
|
||
|
http.Error(w, "Failed to save apps", http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
w.WriteHeader(http.StatusOK)
|
||
|
w.Write([]byte("App deleted successfully"))
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
http.Error(w, "App not found", http.StatusNotFound)
|
||
|
}
|
||
|
|
||
|
func editAppHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
token := r.Header.Get("Authorization")
|
||
|
if token != "Bearer "+bearerToken {
|
||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
||
|
http.Error(w, "Invalid form data", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
appID := r.FormValue("id")
|
||
|
if appID == "" {
|
||
|
http.Error(w, "App ID is required", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var updatedApp *App
|
||
|
mutex.Lock()
|
||
|
for i := range apps {
|
||
|
if apps[i].ID == appID {
|
||
|
updatedApp = &apps[i]
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
mutex.Unlock()
|
||
|
|
||
|
if updatedApp == nil {
|
||
|
http.Error(w, "App not found", http.StatusNotFound)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if name := r.FormValue("name"); name != "" {
|
||
|
updatedApp.Name = name
|
||
|
}
|
||
|
|
||
|
if info := r.FormValue("info"); info != "" {
|
||
|
updatedApp.Info = info
|
||
|
}
|
||
|
|
||
|
// Check if a new file is uploaded
|
||
|
file, handler, err := r.FormFile("file")
|
||
|
if err == nil {
|
||
|
defer file.Close()
|
||
|
|
||
|
customPath := r.FormValue("customPath")
|
||
|
if customPath == "" {
|
||
|
customPath = strings.ReplaceAll(updatedApp.Name, " ", "_")
|
||
|
}
|
||
|
|
||
|
filePath, err := saveUploadedFile(customPath, handler.Filename, file)
|
||
|
if err != nil {
|
||
|
http.Error(w, "Failed to save file", http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Remove the old file
|
||
|
os.Remove(filepath.Join(".", updatedApp.Path))
|
||
|
updatedApp.Path = filePath
|
||
|
}
|
||
|
|
||
|
mutex.Lock()
|
||
|
defer mutex.Unlock()
|
||
|
if err := saveApps(); err != nil {
|
||
|
http.Error(w, "Failed to save app data", http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
w.WriteHeader(http.StatusOK)
|
||
|
json.NewEncoder(w).Encode(updatedApp)
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
if err := loadOrGenerateToken(); err != nil {
|
||
|
log.Fatalf("Error loading or generating token: %v", err)
|
||
|
}
|
||
|
|
||
|
if err := loadApps(); err != nil {
|
||
|
if os.IsNotExist(err) {
|
||
|
log.Println("apps.json not found. Starting with an empty app list.")
|
||
|
} else {
|
||
|
log.Fatalf("Failed to load apps.json: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
http.Handle("/uploads/", http.StripPrefix("/uploads", http.FileServer(http.Dir(uploadDir))))
|
||
|
|
||
|
http.HandleFunc("/apps", func(w http.ResponseWriter, r *http.Request) {
|
||
|
switch r.Method {
|
||
|
case http.MethodGet:
|
||
|
listAppsHandler(w, r)
|
||
|
case http.MethodPost:
|
||
|
uploadAppHandler(w, r)
|
||
|
default:
|
||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
http.HandleFunc("/delete", deleteAppHandler)
|
||
|
http.HandleFunc("/editapp", editAppHandler)
|
||
|
|
||
|
port := os.Getenv("PORT")
|
||
|
fmt.Printf("Server starting on port %s\n", port)
|
||
|
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||
|
}
|
||
|
|