diff --git a/Dockerfile b/Dockerfile index 8860281..87bbf7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,16 @@ -FROM golang:1.25-alpine +FROM python:3.12-slim -#RUN apk add --no-cache openssl +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 WORKDIR /app -COPY go.mod go.sum ./ -RUN go mod download +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt -COPY . . +COPY app.py ./ +COPY templates ./templates -COPY templates/ ./templates/ -COPY static/ ./static/ +EXPOSE 5000 -RUN go build -o /oreshnik ./cmd/oreshnik - -RUN mkdir public -RUN openssl genrsa -out public/private.pem 2048 -RUN openssl rsa -in public/private.pem -pubout -out public/public.pem - -EXPOSE 8080 - -CMD [ "/oreshnik" ] +CMD ["python", "app.py"] diff --git a/README.md b/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/app.py b/app.py new file mode 100644 index 0000000..918eea6 --- /dev/null +++ b/app.py @@ -0,0 +1,50 @@ +from flask import Flask, render_template, request + +app = Flask(__name__) + + +def _calculate(a: float, b: float, op: str) -> float: + if op == "+": + return a + b + if op == "-": + return a - b + if op == "*": + return a * b + if op == "/": + if b == 0: + raise ZeroDivisionError("Division by zero") + return a / b + raise ValueError("Unknown operation") + + +@app.route("/", methods=["GET", "POST"]) +def index(): + result = None + error = None + a_val = "" + b_val = "" + op = "+" + + if request.method == "POST": + a_val = request.form.get("a", "") + b_val = request.form.get("b", "") + op = request.form.get("op", "+") + try: + a = float(a_val) + b = float(b_val) + result = _calculate(a, b, op) + except Exception as exc: # noqa: BLE001 - show a friendly message + error = str(exc) + + return render_template( + "index.html", + result=result, + error=error, + a_val=a_val, + b_val=b_val, + op=op, + ) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=False) diff --git a/cmd/oreshnik/main.go b/cmd/oreshnik/main.go deleted file mode 100644 index ce236d9..0000000 --- a/cmd/oreshnik/main.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - - "oreshnik/internal/app/server" -) - -func main() { - s, err := server.New() - if err != nil { - log.Fatalf("failed to init server: %v", err) - } - fmt.Println("Server started on :8080") - log.Fatal(http.ListenAndServe(":8080", s.Router())) -} diff --git a/docker-compose.yml b/docker-compose.yml index 3dd8f6e..64d77bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,6 @@ services: - app: + web: build: . ports: - - "8099:8080" - depends_on: - - db - environment: - - DB_HOST=db - - DB_USER=goidactf - - DB_PASSWORD=goidactf - - DB_NAME=goidactf - - DB_PORT=5432 - - FLAG=${FLAG} - - db: - image: postgres:13-alpine - environment: - - POSTGRES_USER=goidactf - - POSTGRES_PASSWORD=goidactf - - POSTGRES_DB=goidactf + - "5000:5000" + restart: unless-stopped diff --git a/go.mod b/go.mod deleted file mode 100644 index 92f7e79..0000000 --- a/go.mod +++ /dev/null @@ -1,21 +0,0 @@ -module oreshnik - -go 1.25.4 - -require ( - github.com/golang-jwt/jwt/v4 v4.5.2 - golang.org/x/crypto v0.47.0 - gorm.io/driver/postgres v1.6.0 - gorm.io/gorm v1.31.1 -) - -require ( - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/text v0.33.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index a298eca..0000000 --- a/go.sum +++ /dev/null @@ -1,38 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= -github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= -gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= -gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= -gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/internal/app/auth/jwt.go b/internal/app/auth/jwt.go deleted file mode 100644 index 85d39df..0000000 --- a/internal/app/auth/jwt.go +++ /dev/null @@ -1,58 +0,0 @@ -package auth - -import ( - "crypto/rsa" - "fmt" - "os" - "time" - - "github.com/golang-jwt/jwt/v4" -) - -func LoadKeys() (*rsa.PrivateKey, *rsa.PublicKey, error) { - privBytes, err := os.ReadFile("public/private.pem") - if err != nil { - return nil, nil, fmt.Errorf("read private key: %w", err) - } - priv, err := jwt.ParseRSAPrivateKeyFromPEM(privBytes) - if err != nil { - return nil, nil, fmt.Errorf("parse private key: %w", err) - } - - pubBytes, err := os.ReadFile("public/public.pem") - if err != nil { - return nil, nil, fmt.Errorf("read public key: %w", err) - } - pub, err := jwt.ParseRSAPublicKeyFromPEM(pubBytes) - if err != nil { - return nil, nil, fmt.Errorf("parse public key: %w", err) - } - return priv, pub, nil -} - -func GenerateJWT(userID uint, isAdmin bool, privateKey *rsa.PrivateKey) (string, error) { - claims := jwt.MapClaims{ - "user_id": userID, - "is_admin": isAdmin, - "exp": time.Now().Add(24 * time.Hour).Unix(), - } - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - return token.SignedString(privateKey) -} - -func Parse(tokenString string, publicKey *rsa.PublicKey) (*jwt.Token, jwt.MapClaims, error) { - tok, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { - if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) - } - return publicKey, nil - }) - if err != nil { - return nil, nil, err - } - claims, ok := tok.Claims.(jwt.MapClaims) - if !ok || !tok.Valid { - return nil, nil, fmt.Errorf("invalid token") - } - return tok, claims, nil -} diff --git a/internal/app/db/db.go b/internal/app/db/db.go deleted file mode 100644 index f70e3df..0000000 --- a/internal/app/db/db.go +++ /dev/null @@ -1,41 +0,0 @@ -package db - -import ( - "fmt" - "log" - "os" - "time" - - "gorm.io/driver/postgres" - "gorm.io/gorm" -) - -func ConnectAndMigrate(models ...interface{}) (*gorm.DB, error) { - dsn := fmt.Sprintf( - "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", - os.Getenv("DB_HOST"), - os.Getenv("DB_USER"), - os.Getenv("DB_PASSWORD"), - os.Getenv("DB_NAME"), - os.Getenv("DB_PORT"), - ) - - var database *gorm.DB - var err error - for i := 0; i < 10; i++ { - database, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) - if err == nil { - break - } - log.Println("Failed to connect to database, retrying...") - time.Sleep(2 * time.Second) - } - if err != nil { - return nil, fmt.Errorf("connect database: %w", err) - } - - if err := database.AutoMigrate(models...); err != nil { - return nil, fmt.Errorf("auto migrate: %w", err) - } - return database, nil -} diff --git a/internal/app/models/models.go b/internal/app/models/models.go deleted file mode 100644 index 2a8df38..0000000 --- a/internal/app/models/models.go +++ /dev/null @@ -1,31 +0,0 @@ -package models - -import "gorm.io/gorm" - -type RevokedToken struct { - ID uint `gorm:"primaryKey"` - Token string `gorm:"unique"` -} - -type User struct { - ID uint `gorm:"primaryKey"` - Username string `gorm:"unique"` - Password string - IsAdmin bool `gorm:"default:false"` -} - -type Product struct { - ID uint `gorm:"primaryKey"` - Name string - Description string - Price float64 -} - -type Purchase struct { - gorm.Model - UserID uint - ProductID uint - Product Product `gorm:"foreignKey:ProductID"` -} - -var _ = gorm.Model{} diff --git a/internal/app/server/server.go b/internal/app/server/server.go deleted file mode 100644 index 446bbd8..0000000 --- a/internal/app/server/server.go +++ /dev/null @@ -1,303 +0,0 @@ -package server - -import ( - "crypto/rsa" - "encoding/json" - "html/template" - "log" - "math/rand" - "net/http" - "os" - "strings" - "time" - - "oreshnik/internal/app/auth" - "oreshnik/internal/app/db" - "oreshnik/internal/app/models" - - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" -) - -type Server struct { - DB *gorm.DB - PrivKey *rsa.PrivateKey - PubKey *rsa.PublicKey - Templates *template.Template - Flag string - rand *rand.Rand -} - -func New() (*Server, error) { - s := &Server{Flag: os.Getenv("FLAG")} - if s.Flag == "" { - s.Flag = "goidactf{fake_flag}" - } - - s.Templates = template.Must(template.ParseGlob("templates/*.html")) - - var err error - s.DB, err = db.ConnectAndMigrate(&models.User{}, &models.RevokedToken{}, &models.Product{}, &models.Purchase{}) - if err != nil { - return nil, err - } - - if _, err := os.Stat("public"); os.IsNotExist(err) { - _ = os.Mkdir("public", 0755) - } - - s.PrivKey, s.PubKey, err = auth.LoadKeys() - if err != nil { - return nil, err - } - - s.rand = rand.New(rand.NewSource(time.Now().UnixNano())) - if err := s.seed(); err != nil { - return nil, err - } - return s, nil -} - -func (s *Server) Router() *http.ServeMux { - mux := http.NewServeMux() - mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) - mux.HandleFunc("/", s.homeHandler) - mux.HandleFunc("/register", s.registerHandler) - mux.HandleFunc("/login", s.loginHandler) - mux.HandleFunc("/admin", s.adminHandler) - mux.HandleFunc("/revoked", s.revokedHandler) - mux.HandleFunc("/product/", s.productHandler) - mux.HandleFunc("/buy/", s.buyHandler) - mux.HandleFunc("/logout", s.logoutHandler) - mux.HandleFunc("/my-purchases", s.myPurchasesHandler) - mux.HandleFunc("/order/", s.orderHandler) - return mux -} - -func (s *Server) randomPassword(n int) string { - const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - b := make([]byte, n) - for i := range b { - b[i] = charset[s.rand.Intn(len(charset))] - } - return string(b) -} - -func (s *Server) seed() error { - s.DB.Exec("DELETE FROM revoked_tokens") - s.DB.Exec("DELETE FROM products") - s.DB.Exec("DELETE FROM users") - - products := []models.Product{ - {Name: "Грецкий орех", Description: "Классический орех, богат омега-3. Отлично подходит для выпечки и салатов.", Price: 250.0}, - {Name: "Миндаль", Description: "Полезен для сердца и кожи. Идеален в качестве перекуса или добавки в мюсли.", Price: 400.0}, - {Name: "Кешью", Description: "Сладкий и маслянистый, идеален для закусок и азиатских блюд.", Price: 550.0}, - {Name: "Фисташки", Description: "Соленые и хрустящие, прекрасная закуска к напиткам.", Price: 600.0}, - {Name: "Фундук", Description: "Лесной орех с насыщенным вкусом, хорош в шоколаде и десертах.", Price: 450.0}, - } - if err := s.DB.Create(&products).Error; err != nil { - return err - } - - adminPass := s.randomPassword(16) - hashAdmin, _ := bcrypt.GenerateFromPassword([]byte(adminPass), bcrypt.DefaultCost) - log.Printf("Создан администратор с паролем: %s", adminPass) - admin := models.User{Username: "админ", Password: string(hashAdmin), IsAdmin: true} - if err := s.DB.Create(&admin).Error; err != nil { - return err - } - - users := []models.User{{Username: "пользователь1"}, {Username: "тест"}} - for i := range users { - p := s.randomPassword(16) - h, _ := bcrypt.GenerateFromPassword([]byte(p), bcrypt.DefaultCost) - users[i].Password = string(h) - if err := s.DB.Create(&users[i]).Error; err != nil { - return err - } - log.Printf("Создан пользователь '%s' с паролем: %s", users[i].Username, p) - } - - adminTok, _ := auth.GenerateJWT(admin.ID, admin.IsAdmin, s.PrivKey) - if err := s.DB.Create(&models.RevokedToken{Token: adminTok}).Error; err != nil { - return err - } - return nil -} - -func (s *Server) homeHandler(w http.ResponseWriter, r *http.Request) { - data := s.getTemplateData(r) - if data["LoggedIn"].(bool) { - var products []models.Product - s.DB.Find(&products) - data["Products"] = products - } - _ = s.Templates.ExecuteTemplate(w, "index.html", data) -} - -func (s *Server) registerHandler(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - _ = s.Templates.ExecuteTemplate(w, "register.html", s.getTemplateData(r)) - return - } - _ = r.ParseForm() - username := r.FormValue("username") - password := r.FormValue("password") - h, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - u := models.User{Username: username, Password: string(h)} - if err := s.DB.Create(&u).Error; err != nil { - http.Error(w, "Username already exists", http.StatusConflict) - return - } - http.Redirect(w, r, "/login", http.StatusFound) -} - -func (s *Server) loginHandler(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - _ = s.Templates.ExecuteTemplate(w, "login.html", s.getTemplateData(r)) - return - } - _ = r.ParseForm() - username := r.FormValue("username") - password := r.FormValue("password") - var user models.User - s.DB.First(&user, "username = ?", username) - if user.ID == 0 || bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) != nil { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - tok, _ := auth.GenerateJWT(user.ID, user.IsAdmin, s.PrivKey) - http.SetCookie(w, &http.Cookie{Name: "token", Value: tok, Path: "/"}) - http.Redirect(w, r, "/", http.StatusFound) -} - -func (s *Server) adminHandler(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie("token") - if err != nil { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - tokenString := cookie.Value - var revoked models.RevokedToken - if s.DB.First(&revoked, "token = ?", tokenString).Error == nil { - http.Error(w, "Token has been revoked", http.StatusForbidden) - return - } - _, claims, err := auth.Parse(tokenString, s.PubKey) - if err != nil || !claims["is_admin"].(bool) { - http.Error(w, "Invalid token or not admin", http.StatusForbidden) - return - } - data := s.getTemplateData(r) - data["Flag"] = s.Flag - _ = s.Templates.ExecuteTemplate(w, "admin.html", data) -} - -func (s *Server) revokedHandler(w http.ResponseWriter, r *http.Request) { - var tokens []models.RevokedToken - s.DB.Find(&tokens) - var tokenStrings []string - for _, t := range tokens { - tokenStrings = append(tokenStrings, t.Token) - } - w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(tokenStrings) -} - -func (s *Server) productHandler(w http.ResponseWriter, r *http.Request) { - id := strings.TrimPrefix(r.URL.Path, "/product/") - var product models.Product - s.DB.First(&product, id) - if product.ID == 0 { - http.NotFound(w, r) - return - } - data := s.getTemplateData(r) - data["Product"] = product - _ = s.Templates.ExecuteTemplate(w, "product.html", data) -} - -func (s *Server) buyHandler(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie("token") - if err != nil { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - _, claims, err := auth.Parse(cookie.Value, s.PubKey) - if err != nil { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - userID := uint(claims["user_id"].(float64)) - productID := strings.TrimPrefix(r.URL.Path, "/buy/") - - purchase := models.Purchase{UserID: userID, ProductID: 0} - s.DB.First(&models.Product{}, productID).Scan(&purchase.Product) - if purchase.Product.ID == 0 { - http.NotFound(w, r) - return - } - purchase.ProductID = purchase.Product.ID - - s.DB.Create(&purchase) - - _ = s.Templates.ExecuteTemplate(w, "purchase_success.html", s.getTemplateData(r)) -} - -func (s *Server) logoutHandler(w http.ResponseWriter, r *http.Request) { - http.SetCookie(w, &http.Cookie{Name: "token", Value: "", Path: "/", Expires: time.Unix(0, 0)}) - http.Redirect(w, r, "/", http.StatusFound) -} - -func (s *Server) myPurchasesHandler(w http.ResponseWriter, r *http.Request) { - data := s.getTemplateData(r) - if !data["LoggedIn"].(bool) { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - var purchases []models.Purchase - s.DB.Preload("Product").Where("user_id = ?", data["UserID"]).Find(&purchases) - data["Purchases"] = purchases - - _ = s.Templates.ExecuteTemplate(w, "my_purchases.html", data) -} - -func (s *Server) orderHandler(w http.ResponseWriter, r *http.Request) { - data := s.getTemplateData(r) - if !data["LoggedIn"].(bool) { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - orderID := strings.TrimPrefix(r.URL.Path, "/order/") - var purchase models.Purchase - s.DB.Preload("Product").First(&purchase, orderID) - - data["Purchase"] = purchase - _ = s.Templates.ExecuteTemplate(w, "order_detail.html", data) -} - -func (s *Server) getTemplateData(r *http.Request) map[string]interface{} { - data := map[string]interface{}{ - "LoggedIn": false, - } - cookie, err := r.Cookie("token") - if err != nil { - return data - } - _, claims, err := auth.Parse(cookie.Value, s.PubKey) - if err != nil { - return data - } - var user models.User - s.DB.First(&user, claims["user_id"]) - if user.ID != 0 { - data["LoggedIn"] = true - data["Username"] = user.Username - data["UserID"] = user.ID - } - return data -} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..95fef4e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Flask==3.0.3 diff --git a/static/script.js b/static/script.js deleted file mode 100644 index 1144b7b..0000000 --- a/static/script.js +++ /dev/null @@ -1,14 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - console.log('Магазин "Орешник" загружен и готов к работе!'); - - const productCards = document.querySelectorAll('.product-card-link'); - productCards.forEach(card => { - card.addEventListener('mouseover', () => { - card.style.transform = 'scale(1.05)'; - card.style.transition = 'transform 0.2s'; - }); - card.addEventListener('mouseout', () => { - card.style.transform = 'scale(1)'; - }); - }); -}); diff --git a/static/style.css b/static/style.css deleted file mode 100644 index 50d9dc7..0000000 --- a/static/style.css +++ /dev/null @@ -1,95 +0,0 @@ -body { - font-family: sans-serif; - background-color: #f4f4f4; - color: #333; - margin: 0; - padding: 0; -} - -.container { - max-width: 960px; - margin: 0 auto; - padding: 20px; -} - -header { - background-color: #4a3f35; /* A warmer, nut-brown color */ - color: #fff; - padding: 0 40px; - border-radius: 8px; - margin-bottom: 20px; - display: flex; - justify-content: space-between; - align-items: center; - height: 70px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -header h1 { - margin: 0; - font-size: 1.5em; -} - -.logo { - color: #fff; - text-decoration: none; -} - -nav { - display: flex; - align-items: center; -} - -nav a { - color: #fff; - text-decoration: none; - margin-left: 20px; - padding: 8px 12px; - border-radius: 5px; - transition: background-color 0.3s; -} - -nav a:hover { - background-color: #6d5c4b; -} - -nav span { - margin-left: 20px; - font-weight: bold; -} - -.product-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 20px; -} - -.product-card { - background-color: #fff; - border: 1px solid #ddd; - border-radius: 5px; - padding: 20px; -} - -.price { - font-weight: bold; - color: #0056b3; -} - -.auth-form { - display: flex; - flex-direction: column; - max-width: 300px; -} - -.auth-form input { - margin-bottom: 10px; - padding: 8px; -} - -.flag { - font-family: monospace; - background-color: #eee; - padding: 10px; - border: 1px solid #ccc; -} diff --git a/templates/admin.html b/templates/admin.html deleted file mode 100644 index 1878a76..0000000 --- a/templates/admin.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - Админ-панель - - - - -
-
-

- -
-
-

Секретная информация

-

Флаг: {{.Flag}}

-
-
- - diff --git a/templates/index.html b/templates/index.html index 63f72b4..81cc723 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,45 +1,81 @@ - - - - - Магазин "Орешник" - - - - -
-
-

- -
-
- {{if .LoggedIn}} -

Наши товары

- - {{else}} -

Пожалуйста, войдите, чтобы увидеть наши товары.

- {{end}} -
+ + + + + + Flask Calculator + + + +
+

Calculator

+

Simple Flask calculator. Enter two numbers and choose an operation.

+
+ + + + +
+ + {% if result is not none %} +
Result: {{ result }}
+ {% endif %} + {% if error %} +
Error: {{ error }}
+ {% endif %}
- + diff --git a/templates/login.html b/templates/login.html deleted file mode 100644 index ac0a74c..0000000 --- a/templates/login.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Вход - Магазин "Орешник" - - - - -
-
-

- -
-
-
- - - - - -
-
-
- - diff --git a/templates/my_purchases.html b/templates/my_purchases.html deleted file mode 100644 index 1e06373..0000000 --- a/templates/my_purchases.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - Мои покупки - Магазин "Орешник" - - - - -
-
-

- -
-
-

Мои покупки

- {{if .Purchases}} -
- {{range .Purchases}} - -
-

{{.Product.Name}}

-

Заказ #{{.ID}}

-
-
- {{end}} -
- {{else}} -

Вы еще ничего не купили.

- {{end}} -
-
- - diff --git a/templates/order_detail.html b/templates/order_detail.html deleted file mode 100644 index bcd97d2..0000000 --- a/templates/order_detail.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Детали заказа - Магазин "Орешник" - - - - -
-
-

- -
-
-

Детали заказа #{{.Purchase.ID}}

- {{if .Purchase}} -
-

{{.Purchase.Product.Name}}

-

{{.Purchase.Product.Description}}

-

Цена: {{.Purchase.Product.Price}} руб.

-

Куплено: {{.Purchase.CreatedAt.Format "02.01.2006 15:04"}}

-
- {{else}} -

Заказ не найден.

- {{end}} -
-
- - diff --git a/templates/product.html b/templates/product.html deleted file mode 100644 index d947201..0000000 --- a/templates/product.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - {{.Product.Name}} - Магазин "Орешник" - - - - -
-
-

- -
-
-
-

{{.Product.Description}}

-

Цена: {{.Product.Price}} руб.

-
- -
-
-
-
- - diff --git a/templates/purchase_success.html b/templates/purchase_success.html deleted file mode 100644 index de71aee..0000000 --- a/templates/purchase_success.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - Спасибо за покупку! - - - - -
-
-

- -
-
-

Ваш заказ успешно оформлен.

-
-
- - diff --git a/templates/register.html b/templates/register.html deleted file mode 100644 index 58bce87..0000000 --- a/templates/register.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Регистрация - Магазин "Орешник" - - - - -
-
-

- -
-
-
- - - - - -
-
-
- -