diff --git a/backend/.gitignore b/backend/.gitignore index 25f4cc8..57aa957 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,2 @@ tmp -media +/media diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..2f43241 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,18 @@ +# Prerequisites for development + +- Installing dependencies: `go mod tidy` +- Verifying that the `.env` file is present and well configured. + +## Running migrations + +A migration cli is made available through the package: `./cmd/migrate` +At first it is needed to run: `go run ./cmd/migrate db init` to initialize the +tables needed by golang's bun. + +Then, if there are migrations needed to be applied, run: `go run ./cmd/migrate db migrate` + +Everything should be taken care of. + +## Running the server + +The server can be ran as easily as: `go run .` diff --git a/backend/api/blogs/blogs.go b/backend/api/blogs/blogs.go new file mode 100644 index 0000000..65a8dfe --- /dev/null +++ b/backend/api/blogs/blogs.go @@ -0,0 +1,58 @@ +package blogs + +import ( + "context" + "fmt" + "net/http" + + core "fr.latosa-escrima/api/core" +) + +func HandleBlog(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + blog_uuid := r.PathValue("uuid") + + var blog core.Blog + _, err := core.DB.NewSelect(). + Model(&blog). + Where("blog_id = ?", blog_uuid). + Relation("Author"). + ScanAndCount(context.Background()) + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusNotAcceptable) + return + } + + core.JSONSuccess{ + Status: core.Success, + Message: "Status OK", + Data: blog, + }.Respond(w, http.StatusOK) + return +} + +func HandleGetBlogs(w http.ResponseWriter, r *http.Request) { + var blog []core.Blog + count, err := core.DB.NewSelect(). + Model(&blog). + Relation("Author"). + ScanAndCount(context.Background()) + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusNotAcceptable) + return + } + + core.JSONSuccess{ + Status: core.Success, + Message: fmt.Sprint("%d blogs objects sent", count), + Data: blog, + }.Respond(w, http.StatusOK) + return +} diff --git a/backend/api/delete_blog.go b/backend/api/blogs/delete_blog.go similarity index 100% rename from backend/api/delete_blog.go rename to backend/api/blogs/delete_blog.go diff --git a/backend/api/new_blog.go b/backend/api/blogs/new_blog.go similarity index 71% rename from backend/api/new_blog.go rename to backend/api/blogs/new_blog.go index 78d4d57..96b4916 100644 --- a/backend/api/new_blog.go +++ b/backend/api/blogs/new_blog.go @@ -1,14 +1,22 @@ -package api +package blogs import ( "context" "encoding/json" "net/http" - + "io" core "fr.latosa-escrima/api/core" ) -func HandleCreateBlog(w http.ResponseWriter, r *http.Request) { +func HandleNew(w http.ResponseWriter, r *http.Request) { + _, err := io.ReadAll(r.Body) + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusNoContent) + return + } var blog core.Blog if err := json.NewDecoder(r.Body).Decode(&blog); err != nil { core.JSONError{ diff --git a/backend/api/core/schemas.go b/backend/api/core/schemas.go index a2b6e35..1569ff8 100644 --- a/backend/api/core/schemas.go +++ b/backend/api/core/schemas.go @@ -27,13 +27,6 @@ func (dsn *DSN) ToString() string { return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", dsn.User, dsn.Password, dsn.Hostname, dsn.Port, dsn.DBName) } -type Role string - -const ( - AdminRole Role = "admin" - UserRole Role = "user" -) - type Status string const ( @@ -41,20 +34,35 @@ const ( Inactive Status = "Inactive" ) +type Group string + +const ( + LatosaGroup Group = "latosa" + WingTsunGroup Group = "wing-tsun" +) + +type UserAttributes struct { + Groups []Group `json:"groups"` +} + +type PermissionConditions struct { + Groups *[]Group `json:"groups,omitempty"` +} + type User struct { bun.BaseModel `bun:"table:users"` - UserID uuid.UUID `bun:"type:uuid,pk,default:gen_random_uuid()" json:"userId"` - FirstName string `bun:"firstname,notnull" json:"firstname"` - LastName string `bun:"lastname,notnull" json:"lastname"` - Email string `bun:"email,unique,notnull" json:"email"` - Password string `bun:"password,notnull" json:"password,omitempty"` - Phone string `bun:"phone,notnull" json:"phone"` - Role Role `bun:"role,notnull,default:'user'" json:"role"` - CreatedAt time.Time `bun:"created_at,default:current_timestamp" json:"createdAt"` - UpdatedAt time.Time `bun:"updated_at,default:current_timestamp" json:"updatedAt"` - Events []Event `bun:"m2m:events_to_users,join:User=Event" json:"events,omitempty"` - Articles []*Blog `bun:"rel:has-many,join:user_id=blog_id" json:"articles,omitempty"` + UserID uuid.UUID `bun:"type:uuid,pk,default:gen_random_uuid()" json:"userId"` + FirstName string `bun:"firstname,notnull" json:"firstname"` + LastName string `bun:"lastname,notnull" json:"lastname"` + Email string `bun:"email,unique,notnull" json:"email"` + Password string `bun:"password,notnull" json:"password,omitempty"` + Phone string `bun:"phone,notnull" json:"phone"` + CreatedAt time.Time `bun:"created_at,default:current_timestamp" json:"createdAt"` + UpdatedAt time.Time `bun:"updated_at,default:current_timestamp" json:"updatedAt"` + Events []Event `bun:"m2m:events_to_users,join:User=Event" json:"events,omitempty"` + Articles []*Blog `bun:"rel:has-many,join:user_id=blog_id" json:"articles,omitempty"` + Attributes UserAttributes `bun:"attributes,type:jsonb" json:"attributes"` } func (u *User) Insert(ctx context.Context) (sql.Result, error) { @@ -66,15 +74,6 @@ func (u *User) Insert(ctx context.Context) (sql.Result, error) { } func Verify(ctx context.Context, email, password string) (*User, error) { - // var user User - // query := ` - // SELECT * - // FROM users - // WHERE email = ? AND password = crypt(?, password) - // ` - // - // err := DB.NewRaw(query, email, password).Scan(ctx, user) - var user User count, err := DB.NewSelect(). Model(&user). @@ -94,6 +93,40 @@ func Verify(ctx context.Context, email, password string) (*User, error) { return &user, nil } +type Permission struct { + bun.BaseModel `bun:"table:permissions"` + ID int `bun:"id,pk,autoincrement" json:"id"` + Resource string `bun:"resource,notnull" json:"resource"` + Action string `bun:"action,notnull" json:"action"` + Conditions PermissionConditions `bun:"conditions,type:jsonb" json:"conditions"` +} + +type Role struct { + bun.BaseModel `bun:"table:roles"` + ID uuid.UUID `bun:"id,pk,type:uuid,default:gen_random_uuid()" json:"id"` + Name string `bun:"name,unique,notnull" json:"name"` +} + +type PermissionToRole struct { + bun.BaseModel `bun:"table:permissions_to_users"` + + PermissionID int `bun:"permission_id,pk"` + RoleID uuid.UUID `bun:"type:uuid,pk"` + + Permission *Permission `bun:"rel:belongs-to,join:permission_id=id"` + Role *Role `bun:"rel:belongs-to,join:role_id=id"` +} + +type UserToRole struct { + bun.BaseModel `bun:"table:users_to_roles"` + + UserID uuid.UUID `bun:"user_id,type:uuid,pk"` + RoleID uuid.UUID `bun:"type:uuid,pk"` + + User *User `bun:"rel:belongs-to,join:user_id=user_id"` + Role *Role `bun:"rel:belongs-to,join:role_id=id"` +} + type Event struct { bun.BaseModel `bun:"table:events"` @@ -188,6 +221,8 @@ func InitDatabase(dsn DSN) (*bun.DB, error) { return nil, err } db.RegisterModel((*EventToUser)(nil)) + db.RegisterModel((*PermissionToRole)(nil)) + db.RegisterModel((*UserToRole)(nil)) _, err = db.NewCreateTable().Model((*User)(nil)).IfNotExists().Exec(ctx) _, err = db.NewCreateTable().Model((*Event)(nil)).IfNotExists().Exec(ctx) _, err = db.NewCreateTable().Model((*EventToUser)(nil)).IfNotExists().Exec(ctx) @@ -195,6 +230,10 @@ func InitDatabase(dsn DSN) (*bun.DB, error) { _, err = db.NewCreateTable().Model((*WebsiteSettings)(nil)).IfNotExists().Exec(ctx) _, err = db.NewCreateTable().Model((*Media)(nil)).IfNotExists().Exec(ctx) _, err = db.NewCreateTable().Model((*Shortcode)(nil)).IfNotExists().Exec(ctx) + _, err = db.NewCreateTable().Model((*Role)(nil)).IfNotExists().Exec(ctx) + _, err = db.NewCreateTable().Model((*Permission)(nil)).IfNotExists().Exec(ctx) + _, err = db.NewCreateTable().Model((*PermissionToRole)(nil)).IfNotExists().Exec(ctx) + _, err = db.NewCreateTable().Model((*UserToRole)(nil)).IfNotExists().Exec(ctx) if err != nil { return nil, err } diff --git a/backend/api/delete_event.go b/backend/api/events/delete.go similarity index 87% rename from backend/api/delete_event.go rename to backend/api/events/delete.go index 578229d..ca57870 100644 --- a/backend/api/delete_event.go +++ b/backend/api/events/delete.go @@ -1,4 +1,4 @@ -package api +package events import ( "context" @@ -8,7 +8,7 @@ import ( "fr.latosa-escrima/api/core" ) -func HandleDeleteEvent(w http.ResponseWriter, r *http.Request) { +func HandleDelete(w http.ResponseWriter, r *http.Request) { uuid := r.PathValue("event_uuid") var event core.Event res, err := core.DB.NewDelete(). diff --git a/backend/api/get_event.go b/backend/api/events/event.go similarity index 87% rename from backend/api/get_event.go rename to backend/api/events/event.go index 56bbc11..796a987 100644 --- a/backend/api/get_event.go +++ b/backend/api/events/event.go @@ -1,4 +1,4 @@ -package api +package events import ( "context" @@ -8,7 +8,7 @@ import ( core "fr.latosa-escrima/api/core" ) -func HandleGetEvent(w http.ResponseWriter, r *http.Request) { +func HandleEvent(w http.ResponseWriter, r *http.Request) { event_uuid := r.PathValue("event_uuid") var event core.Event _, err := core.DB.NewSelect().Model(&event).Where("uuid = ?", event_uuid).ScanAndCount(context.Background()) @@ -28,7 +28,7 @@ func HandleGetEvent(w http.ResponseWriter, r *http.Request) { return } -func HangleGetEvents(w http.ResponseWriter, r *http.Request) { +func HandleEvents(w http.ResponseWriter, r *http.Request) { var events []core.Event rowsCount, err := core.DB.NewSelect().Model(&events).ScanAndCount(context.Background()) if err != nil { @@ -38,7 +38,7 @@ func HangleGetEvents(w http.ResponseWriter, r *http.Request) { }.Respond(w, http.StatusInternalServerError) return } - + core.JSONSuccess{ Status: core.Success, Message: fmt.Sprintf("%d Event successfully sent", rowsCount), @@ -46,4 +46,3 @@ func HangleGetEvents(w http.ResponseWriter, r *http.Request) { }.Respond(w, http.StatusOK) return } - diff --git a/backend/api/new_event.go b/backend/api/events/new.go similarity index 89% rename from backend/api/new_event.go rename to backend/api/events/new.go index cade7c5..c253e11 100644 --- a/backend/api/new_event.go +++ b/backend/api/events/new.go @@ -1,4 +1,4 @@ -package api +package events import ( "context" @@ -8,7 +8,7 @@ import ( core "fr.latosa-escrima/api/core" ) -func HandleCreateEvent(w http.ResponseWriter, r *http.Request) { +func HandleNew(w http.ResponseWriter, r *http.Request) { var event core.Event err := json.NewDecoder(r.Body).Decode(&event) if err != nil { diff --git a/backend/api/update_event.go b/backend/api/events/update.go similarity index 94% rename from backend/api/update_event.go rename to backend/api/events/update.go index 753f40a..58dd47e 100644 --- a/backend/api/update_event.go +++ b/backend/api/events/update.go @@ -1,4 +1,4 @@ -package api +package events import ( "context" @@ -10,7 +10,7 @@ import ( "github.com/google/uuid" ) -func HandleUpdateEvent(w http.ResponseWriter, r *http.Request) { +func HandleUpdate(w http.ResponseWriter, r *http.Request) { var event core.Event err := json.NewDecoder(r.Body).Decode(&event) if err != nil { diff --git a/backend/api/get_blog.go b/backend/api/get_blog.go index 540ec74..65a8dfe 100644 --- a/backend/api/get_blog.go +++ b/backend/api/get_blog.go @@ -1,4 +1,4 @@ -package api +package blogs import ( "context" @@ -8,10 +8,12 @@ import ( core "fr.latosa-escrima/api/core" ) -func HandleGetBlog(w http.ResponseWriter, r *http.Request) { +func HandleBlog(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + blog_uuid := r.PathValue("uuid") - - var blog core.Blog + + var blog core.Blog _, err := core.DB.NewSelect(). Model(&blog). Where("blog_id = ?", blog_uuid). @@ -19,18 +21,18 @@ func HandleGetBlog(w http.ResponseWriter, r *http.Request) { ScanAndCount(context.Background()) if err != nil { core.JSONError{ - Status: core.Error, + Status: core.Error, Message: err.Error(), }.Respond(w, http.StatusNotAcceptable) - return + return } - + core.JSONSuccess{ Status: core.Success, Message: "Status OK", - Data: blog, + Data: blog, }.Respond(w, http.StatusOK) - return + return } func HandleGetBlogs(w http.ResponseWriter, r *http.Request) { diff --git a/backend/api/delete_media.go b/backend/api/media/delete.go similarity index 90% rename from backend/api/delete_media.go rename to backend/api/media/delete.go index cdf66da..fbb6903 100644 --- a/backend/api/delete_media.go +++ b/backend/api/media/delete.go @@ -1,4 +1,4 @@ -package api +package media import ( "context" @@ -9,7 +9,7 @@ import ( "fr.latosa-escrima/api/core" ) -func HandleDeleteMedia(w http.ResponseWriter, r *http.Request) { +func HandleDelete(w http.ResponseWriter, r *http.Request) { uuid := r.PathValue("media_uuid") var media core.Media res, err := core.DB.NewDelete(). diff --git a/backend/api/get_media.go b/backend/api/media/media.go similarity index 91% rename from backend/api/get_media.go rename to backend/api/media/media.go index 42f0012..ce36d05 100644 --- a/backend/api/get_media.go +++ b/backend/api/media/media.go @@ -1,4 +1,4 @@ -package api +package media import ( "context" @@ -11,7 +11,7 @@ import ( "fr.latosa-escrima/utils" ) -func HandleGetMedia(w http.ResponseWriter, r *http.Request) { +func HandleMedia(w http.ResponseWriter, r *http.Request) { queryParams := r.URL.Query() page, err := strconv.Atoi(queryParams.Get("page")) limit, err := strconv.Atoi(queryParams.Get("limit")) @@ -69,7 +69,7 @@ func HandleGetMedia(w http.ResponseWriter, r *http.Request) { }.Respond(w, http.StatusOK) } -func HandleGetMediaDetails(w http.ResponseWriter, r *http.Request) { +func HandleMediaDetails(w http.ResponseWriter, r *http.Request) { uuid := r.PathValue("media_uuid") var media core.Media err := core.DB.NewSelect(). @@ -96,7 +96,7 @@ func HandleGetMediaDetails(w http.ResponseWriter, r *http.Request) { }.Respond(w, http.StatusOK) } -func HandleGetMediaFile(w http.ResponseWriter, r *http.Request) { +func HandleMediaFile(w http.ResponseWriter, r *http.Request) { uuid := r.PathValue("media_uuid") var media core.Media err := core.DB.NewSelect(). diff --git a/backend/api/media/update.go b/backend/api/media/update.go new file mode 100644 index 0000000..83e39a0 --- /dev/null +++ b/backend/api/media/update.go @@ -0,0 +1,3 @@ +package media + +// TODO diff --git a/backend/api/upload_media.go b/backend/api/media/upload.go similarity index 97% rename from backend/api/upload_media.go rename to backend/api/media/upload.go index bbdfd45..91fc6db 100644 --- a/backend/api/upload_media.go +++ b/backend/api/media/upload.go @@ -1,4 +1,4 @@ -package api +package media import ( "context" @@ -14,7 +14,7 @@ import ( "github.com/google/uuid" ) -func HandleUploadMedia(w http.ResponseWriter, r *http.Request) { +func HandleUpload(w http.ResponseWriter, r *http.Request) { // Parse the multipart form err := r.ParseMultipartForm(10 << 20) // Limit file size to 10 MB if err != nil { diff --git a/backend/api/verify_media.go b/backend/api/media/verify.go similarity index 93% rename from backend/api/verify_media.go rename to backend/api/media/verify.go index 3f29876..d0c37b1 100644 --- a/backend/api/verify_media.go +++ b/backend/api/media/verify.go @@ -1,4 +1,4 @@ -package api +package media import ( "encoding/json" @@ -18,7 +18,7 @@ type FileArgs struct { SizeByte int64 `json:"size"` } -func HandleVerifyMedia(w http.ResponseWriter, r *http.Request) { +func HandleVerify(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { core.JSONError{ diff --git a/backend/api/auth.go b/backend/api/middlewares.go similarity index 52% rename from backend/api/auth.go rename to backend/api/middlewares.go index 87919ec..1296c47 100644 --- a/backend/api/auth.go +++ b/backend/api/middlewares.go @@ -2,99 +2,33 @@ package api import ( "context" - "encoding/json" "fmt" - "io" "net/http" + "os" "strings" - "time" core "fr.latosa-escrima/api/core" + "fr.latosa-escrima/api/users" "fr.latosa-escrima/utils" "github.com/golang-jwt/jwt/v5" ) -var MySigningKey = []byte("COUCOU") - -type LoginArgs struct { - Email string `json:"email"` - Password string `json:"password"` -} - -type Claims struct { - UserID string `json:"user_id"` - jwt.RegisteredClaims -} - -func HandleLogin(w http.ResponseWriter, r *http.Request) { - if r.Body == nil { - core.JSONError{ - Status: core.Error, - Message: "No body has been provided.", - }.Respond(w, http.StatusNoContent) - return - } - - body, err := io.ReadAll(r.Body) - fmt.Println(body) - if err != nil { - core.JSONError{ - Status: core.Error, - Message: err.Error(), - }.Respond(w, http.StatusNoContent) - return - } - var login LoginArgs - err = json.Unmarshal(body, &login) - if err != nil { - core.JSONError{ - Status: core.Error, - Message: err.Error(), - }.Respond(w, http.StatusNoContent) - return - } - - user, err := core.Verify(context.Background(), login.Email, login.Password) - if user == nil { - core.JSONError{ - Status: core.Error, - Message: "User not found.", - }.Respond(w, http.StatusNotFound) - return - } - if err != nil { - core.JSONError{ - Status: core.Error, - Message: err.Error(), - }.Respond(w, http.StatusNoContent) - return - } - - claims := Claims{ - UserID: user.UserID.String(), - RegisteredClaims: jwt.RegisteredClaims{ - Issuer: "latosa-escrima.fr", - Subject: "authentification", - ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), - IssuedAt: jwt.NewNumericDate(time.Now()), - }, - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - signed, err := token.SignedString(MySigningKey) - if err != nil { - core.JSONError{ - Status: core.Error, - Message: err.Error(), - }.Respond(w, http.StatusNoContent) - return - } - - core.JSONSuccess{ - Status: core.Success, - Message: "JWT Created", - Data: signed, - }.Respond(w, http.StatusCreated) +func CORS(next http.Handler) http.Handler { + CORS_AllowOrigin := os.Getenv("CORS_AllowOrigin") + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Allow all origins (can restrict to specific origins) + w.Header().Set("Access-Control-Allow-Origin", CORS_AllowOrigin) + // Allow certain HTTP methods (you can customize these as needed) + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") + // Allow certain headers (you can add more as needed) + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-CSRF-Token") + w.Header().Set("Access-Control-Allow-Credentials", "true") + // Handle OPTIONS pre-flight request + if r.Method == http.MethodOptions { + return + } + next.ServeHTTP(w, r) + }) } func AuthJWT(next http.Handler) http.Handler { @@ -125,7 +59,7 @@ func AuthJWT(next http.Handler) http.Handler { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } - return MySigningKey, nil + return users.MySigningKey, nil }) if err != nil || !token.Valid { diff --git a/backend/api/permissions/delete.go b/backend/api/permissions/delete.go new file mode 100644 index 0000000..1b7b952 --- /dev/null +++ b/backend/api/permissions/delete.go @@ -0,0 +1 @@ +package permissions diff --git a/backend/api/permissions/new.go b/backend/api/permissions/new.go new file mode 100644 index 0000000..1b7b952 --- /dev/null +++ b/backend/api/permissions/new.go @@ -0,0 +1 @@ +package permissions diff --git a/backend/api/permissions/permission.go b/backend/api/permissions/permission.go new file mode 100644 index 0000000..1b7b952 --- /dev/null +++ b/backend/api/permissions/permission.go @@ -0,0 +1 @@ +package permissions diff --git a/backend/api/permissions/permissions.go b/backend/api/permissions/permissions.go new file mode 100644 index 0000000..1b7b952 --- /dev/null +++ b/backend/api/permissions/permissions.go @@ -0,0 +1 @@ +package permissions diff --git a/backend/api/permissions/update.go b/backend/api/permissions/update.go new file mode 100644 index 0000000..1b7b952 --- /dev/null +++ b/backend/api/permissions/update.go @@ -0,0 +1 @@ +package permissions diff --git a/backend/api/roles/add_permission.go b/backend/api/roles/add_permission.go new file mode 100644 index 0000000..3258a66 --- /dev/null +++ b/backend/api/roles/add_permission.go @@ -0,0 +1 @@ +package roles diff --git a/backend/api/roles/delete.go b/backend/api/roles/delete.go new file mode 100644 index 0000000..3258a66 --- /dev/null +++ b/backend/api/roles/delete.go @@ -0,0 +1 @@ +package roles diff --git a/backend/api/roles/new.go b/backend/api/roles/new.go new file mode 100644 index 0000000..3258a66 --- /dev/null +++ b/backend/api/roles/new.go @@ -0,0 +1 @@ +package roles diff --git a/backend/api/roles/permissions.go b/backend/api/roles/permissions.go new file mode 100644 index 0000000..3258a66 --- /dev/null +++ b/backend/api/roles/permissions.go @@ -0,0 +1 @@ +package roles diff --git a/backend/api/roles/remove_permission.go b/backend/api/roles/remove_permission.go new file mode 100644 index 0000000..3258a66 --- /dev/null +++ b/backend/api/roles/remove_permission.go @@ -0,0 +1 @@ +package roles diff --git a/backend/api/roles/role.go b/backend/api/roles/role.go new file mode 100644 index 0000000..3258a66 --- /dev/null +++ b/backend/api/roles/role.go @@ -0,0 +1 @@ +package roles diff --git a/backend/api/roles/roles.go b/backend/api/roles/roles.go new file mode 100644 index 0000000..3258a66 --- /dev/null +++ b/backend/api/roles/roles.go @@ -0,0 +1 @@ +package roles diff --git a/backend/api/roles/update.go b/backend/api/roles/update.go new file mode 100644 index 0000000..3258a66 --- /dev/null +++ b/backend/api/roles/update.go @@ -0,0 +1 @@ +package roles diff --git a/backend/api/delete_shortcode.go b/backend/api/shortcodes/delete.go similarity index 85% rename from backend/api/delete_shortcode.go rename to backend/api/shortcodes/delete.go index c6a8346..67ea9fb 100644 --- a/backend/api/delete_shortcode.go +++ b/backend/api/shortcodes/delete.go @@ -1,4 +1,4 @@ -package api +package shortcodes import ( "context" @@ -7,7 +7,7 @@ import ( core "fr.latosa-escrima/api/core" ) -func HandleDeleteShortcode(w http.ResponseWriter, r *http.Request) { +func HandleDelete(w http.ResponseWriter, r *http.Request) { code := r.PathValue("shortcode") _, err := core.DB.NewDelete(). Model((*core.Shortcode)(nil)). diff --git a/backend/api/new_shortcode.go b/backend/api/shortcodes/new.go similarity index 90% rename from backend/api/new_shortcode.go rename to backend/api/shortcodes/new.go index 471e8a6..2663bca 100644 --- a/backend/api/new_shortcode.go +++ b/backend/api/shortcodes/new.go @@ -1,4 +1,4 @@ -package api +package shortcodes import ( "context" @@ -8,7 +8,7 @@ import ( "fr.latosa-escrima/api/core" ) -func HandleCreateShortcode(w http.ResponseWriter, r *http.Request) { +func HandleNew(w http.ResponseWriter, r *http.Request) { var shortcode core.Shortcode err := json.NewDecoder(r.Body).Decode(&shortcode) if err != nil { diff --git a/backend/api/get_shortcode.go b/backend/api/shortcodes/shortcode.go similarity index 86% rename from backend/api/get_shortcode.go rename to backend/api/shortcodes/shortcode.go index 1fdcc72..83b2645 100644 --- a/backend/api/get_shortcode.go +++ b/backend/api/shortcodes/shortcode.go @@ -1,4 +1,4 @@ -package api +package shortcodes import ( "context" @@ -7,7 +7,7 @@ import ( "fr.latosa-escrima/api/core" ) -func HandleGetShortcode(w http.ResponseWriter, r *http.Request) { +func HandleShortcode(w http.ResponseWriter, r *http.Request) { code := r.PathValue("shortcode") var shortcode core.Shortcode err := core.DB.NewSelect(). diff --git a/backend/api/get_shortcodes.go b/backend/api/shortcodes/shortcodes.go similarity index 84% rename from backend/api/get_shortcodes.go rename to backend/api/shortcodes/shortcodes.go index 2c48c7f..e27d0c7 100644 --- a/backend/api/get_shortcodes.go +++ b/backend/api/shortcodes/shortcodes.go @@ -1,4 +1,4 @@ -package api +package shortcodes import ( "context" @@ -7,7 +7,7 @@ import ( "fr.latosa-escrima/api/core" ) -func HandleGetShortcodes(w http.ResponseWriter, r *http.Request) { +func HandleShortcodes(w http.ResponseWriter, r *http.Request) { var shortcodes []core.Shortcode err := core.DB.NewSelect().Model(&shortcodes).Scan(context.Background()) if err != nil { diff --git a/backend/api/update_shortcode.go b/backend/api/shortcodes/update.go similarity index 95% rename from backend/api/update_shortcode.go rename to backend/api/shortcodes/update.go index 17c98f9..08dc035 100644 --- a/backend/api/update_shortcode.go +++ b/backend/api/shortcodes/update.go @@ -1,4 +1,4 @@ -package api +package shortcodes import ( "context" @@ -20,7 +20,7 @@ type UpdateShortcodeArgs struct { MediaID *uuid.UUID `json:"media_id,omitempty"` // Nullable reference to another table's ID } -func HandleUpdateShortcode(w http.ResponseWriter, r *http.Request) { +func HandleUpdate(w http.ResponseWriter, r *http.Request) { var updateArgs UpdateShortcodeArgs err := json.NewDecoder(r.Body).Decode(&updateArgs) if err != nil { diff --git a/backend/api/users/auth.go b/backend/api/users/auth.go new file mode 100644 index 0000000..e6cfeb0 --- /dev/null +++ b/backend/api/users/auth.go @@ -0,0 +1,96 @@ +package users + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + core "fr.latosa-escrima/api/core" + "github.com/golang-jwt/jwt/v5" +) + +var MySigningKey = []byte("COUCOU") + +type LoginArgs struct { + Email string `json:"email"` + Password string `json:"password"` +} + +type Claims struct { + UserID string `json:"user_id"` + jwt.RegisteredClaims +} + +func HandleLogin(w http.ResponseWriter, r *http.Request) { + if r.Body == nil { + core.JSONError{ + Status: core.Error, + Message: "No body has been provided.", + }.Respond(w, http.StatusNoContent) + return + } + + body, err := io.ReadAll(r.Body) + fmt.Println(body) + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusNoContent) + return + } + var login LoginArgs + err = json.Unmarshal(body, &login) + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusNoContent) + return + } + + user, err := core.Verify(context.Background(), login.Email, login.Password) + if user == nil { + core.JSONError{ + Status: core.Error, + Message: "User not found.", + }.Respond(w, http.StatusNotFound) + return + } + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusNoContent) + return + } + + claims := Claims{ + UserID: user.UserID.String(), + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: "latosa-escrima.fr", + Subject: "authentification", + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + signed, err := token.SignedString(MySigningKey) + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusNoContent) + return + } + + core.JSONSuccess{ + Status: core.Success, + Message: "JWT Created", + Data: signed, + }.Respond(w, http.StatusCreated) +} diff --git a/backend/api/delete_user.go b/backend/api/users/delete.go similarity index 87% rename from backend/api/delete_user.go rename to backend/api/users/delete.go index 23f1ffd..766a150 100644 --- a/backend/api/delete_user.go +++ b/backend/api/users/delete.go @@ -1,4 +1,4 @@ -package api +package users import ( "context" @@ -7,7 +7,7 @@ import ( "fr.latosa-escrima/api/core" ) -func HandleDeleteUser(w http.ResponseWriter, r *http.Request) { +func HandleDelete(w http.ResponseWriter, r *http.Request) { uuid := r.PathValue("user_uuid") _, err := core.DB.NewDelete(). Model((*core.User)(nil)). diff --git a/backend/api/get_me.go b/backend/api/users/me.go similarity index 85% rename from backend/api/get_me.go rename to backend/api/users/me.go index 6ad406e..dfc6c97 100644 --- a/backend/api/get_me.go +++ b/backend/api/users/me.go @@ -1,4 +1,4 @@ -package api +package users import ( "net/http" @@ -7,7 +7,7 @@ import ( "github.com/golang-jwt/jwt/v5" ) -func HandleGetMe(w http.ResponseWriter, r *http.Request) { +func HandleMe(w http.ResponseWriter, r *http.Request) { token, ok := r.Context().Value("token").(*jwt.Token) if !ok { core.JSONError{ @@ -29,5 +29,5 @@ func HandleGetMe(w http.ResponseWriter, r *http.Request) { uuid := claims["user_id"].(string) r.SetPathValue("user_uuid", uuid) - HandleGetUser(w, r) + HandleUser(w, r) } diff --git a/backend/api/new_user.go b/backend/api/users/new.go similarity index 91% rename from backend/api/new_user.go rename to backend/api/users/new.go index 7c35ad0..ed31f09 100644 --- a/backend/api/new_user.go +++ b/backend/api/users/new.go @@ -1,4 +1,4 @@ -package api +package users import ( "context" @@ -9,7 +9,7 @@ import ( core "fr.latosa-escrima/api/core" ) -func HandleCreateUser(w http.ResponseWriter, r *http.Request) { +func HandleNew(w http.ResponseWriter, r *http.Request) { var user core.User err := json.NewDecoder(r.Body).Decode(&user) if err != nil { diff --git a/backend/api/update_user.go b/backend/api/users/update.go similarity index 78% rename from backend/api/update_user.go rename to backend/api/users/update.go index 9d2397e..78ed85f 100644 --- a/backend/api/update_user.go +++ b/backend/api/users/update.go @@ -1,4 +1,4 @@ -package api +package users import ( "context" @@ -13,15 +13,15 @@ import ( ) type UpdateUserArgs struct { - FirstName *string `json:"firstname,omitempty"` - LastName *string `json:"lastname,omitempty"` - Email *string `json:"email,omitempty"` - Password *string `json:"password,omitempty"` - Phone *string `json:"phone,omitempty"` - Role *core.Role `json:"role,omitempty"` + FirstName *string `json:"firstname,omitempty"` + LastName *string `json:"lastname,omitempty"` + Email *string `json:"email,omitempty"` + Password *string `json:"password,omitempty"` + Phone *string `json:"phone,omitempty"` + Attributes *core.UserAttributes `json:"attributes"` } -func HandleUpdateUser(w http.ResponseWriter, r *http.Request) { +func HandleUpdate(w http.ResponseWriter, r *http.Request) { var updateArgs UpdateUserArgs err := json.NewDecoder(r.Body).Decode(&updateArgs) if err != nil { diff --git a/backend/api/get_user.go b/backend/api/users/user.go similarity index 90% rename from backend/api/get_user.go rename to backend/api/users/user.go index 71b8eb9..4ae00cc 100644 --- a/backend/api/get_user.go +++ b/backend/api/users/user.go @@ -1,4 +1,4 @@ -package api +package users import ( "context" @@ -7,7 +7,7 @@ import ( "fr.latosa-escrima/api/core" ) -func HandleGetUser(w http.ResponseWriter, r *http.Request) { +func HandleUser(w http.ResponseWriter, r *http.Request) { uuid := r.PathValue("user_uuid") var user core.User count, err := core.DB.NewSelect(). diff --git a/backend/api/get_users.go b/backend/api/users/users.go similarity index 79% rename from backend/api/get_users.go rename to backend/api/users/users.go index 707807e..e368f7c 100644 --- a/backend/api/get_users.go +++ b/backend/api/users/users.go @@ -1,4 +1,4 @@ -package api +package users import ( "context" @@ -8,7 +8,7 @@ import ( "fr.latosa-escrima/utils" ) -func HandleGetUsers(w http.ResponseWriter, r *http.Request) { +func HandleUsers(w http.ResponseWriter, r *http.Request) { var users []core.User count, err := core.DB.NewSelect(). Model(&users). @@ -20,9 +20,9 @@ func HandleGetUsers(w http.ResponseWriter, r *http.Request) { }) if count == 0 { - core.JSONError{ - Status: core.Error, - Message: "Not users.", + core.JSONSuccess{ + Status: core.Success, + Message: "No users.", }.Respond(w, http.StatusNotFound) return } @@ -35,7 +35,6 @@ func HandleGetUsers(w http.ResponseWriter, r *http.Request) { return } - // TODO : Remove password core.JSONSuccess{ Status: core.Success, Message: "Users found.", diff --git a/backend/cmd/migrate/main.go b/backend/cmd/migrate/main.go new file mode 100644 index 0000000..a3ee4e6 --- /dev/null +++ b/backend/cmd/migrate/main.go @@ -0,0 +1,219 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "os" + "strings" + + "fr.latosa-escrima/api/core" + "fr.latosa-escrima/cmd/migrate/migrations" + "github.com/joho/godotenv" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/driver/pgdriver" + "github.com/uptrace/bun/extra/bundebug" + "github.com/uptrace/bun/migrate" + + "github.com/urfave/cli/v2" + + "github.com/uptrace/bun" +) + +func main() { + err := godotenv.Load() + if err != nil { + log.Fatalf("Error loading .env file: %v", err) + } + environ := os.Getenv("ENVIRONMENT") + + hostname := os.Getenv("DATABASE_HOSTNAME") + postgres_port := os.Getenv("POSTGRES_DOCKER_PORT") + if environ == "DEV" { + hostname = "localhost" + postgres_port = os.Getenv("POSTGRES_PORT") + } + + dsn := core.DSN{ + Hostname: hostname, + Port: postgres_port, + DBName: os.Getenv("POSTGRES_DB"), + User: os.Getenv("POSTGRES_USER"), + Password: os.Getenv("POSTGRES_PASSWORD"), + } + fmt.Println(dsn.ToString()) + sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn.ToString()))) + db := bun.NewDB(sqldb, pgdialect.New()) + + db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) + + defer db.Close() + if err != nil { + panic(err) + } + + db.AddQueryHook(bundebug.NewQueryHook( + bundebug.WithEnabled(false), + bundebug.FromEnv(), + )) + + app := &cli.App{ + Name: "bun", + + Commands: []*cli.Command{ + newDBCommand(migrate.NewMigrator(db, migrations.Migrations)), + }, + } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} + +func newDBCommand(migrator *migrate.Migrator) *cli.Command { + return &cli.Command{ + Name: "db", + Usage: "database migrations", + Subcommands: []*cli.Command{ + { + Name: "init", + Usage: "create migration tables", + Action: func(c *cli.Context) error { + return migrator.Init(c.Context) + }, + }, + { + Name: "migrate", + Usage: "migrate database", + Action: func(c *cli.Context) error { + if err := migrator.Lock(c.Context); err != nil { + return err + } + defer migrator.Unlock(c.Context) //nolint:errcheck + + group, err := migrator.Migrate(c.Context) + if err != nil { + return err + } + if group.IsZero() { + fmt.Printf("there are no new migrations to run (database is up to date)\n") + return nil + } + fmt.Printf("migrated to %s\n", group) + return nil + }, + }, + { + Name: "rollback", + Usage: "rollback the last migration group", + Action: func(c *cli.Context) error { + if err := migrator.Lock(c.Context); err != nil { + return err + } + defer migrator.Unlock(c.Context) //nolint:errcheck + + group, err := migrator.Rollback(c.Context) + if err != nil { + return err + } + if group.IsZero() { + fmt.Printf("there are no groups to roll back\n") + return nil + } + fmt.Printf("rolled back %s\n", group) + return nil + }, + }, + { + Name: "lock", + Usage: "lock migrations", + Action: func(c *cli.Context) error { + return migrator.Lock(c.Context) + }, + }, + { + Name: "unlock", + Usage: "unlock migrations", + Action: func(c *cli.Context) error { + return migrator.Unlock(c.Context) + }, + }, + { + Name: "create_go", + Usage: "create Go migration", + Action: func(c *cli.Context) error { + name := strings.Join(c.Args().Slice(), "_") + mf, err := migrator.CreateGoMigration(c.Context, name) + if err != nil { + return err + } + fmt.Printf("created migration %s (%s)\n", mf.Name, mf.Path) + return nil + }, + }, + { + Name: "create_sql", + Usage: "create up and down SQL migrations", + Action: func(c *cli.Context) error { + name := strings.Join(c.Args().Slice(), "_") + files, err := migrator.CreateSQLMigrations(c.Context, name) + if err != nil { + return err + } + + for _, mf := range files { + fmt.Printf("created migration %s (%s)\n", mf.Name, mf.Path) + } + + return nil + }, + }, + { + Name: "create_tx_sql", + Usage: "create up and down transactional SQL migrations", + Action: func(c *cli.Context) error { + name := strings.Join(c.Args().Slice(), "_") + files, err := migrator.CreateTxSQLMigrations(c.Context, name) + if err != nil { + return err + } + + for _, mf := range files { + fmt.Printf("created transaction migration %s (%s)\n", mf.Name, mf.Path) + } + + return nil + }, + }, + { + Name: "status", + Usage: "print migrations status", + Action: func(c *cli.Context) error { + ms, err := migrator.MigrationsWithStatus(c.Context) + if err != nil { + return err + } + fmt.Printf("migrations: %s\n", ms) + fmt.Printf("unapplied migrations: %s\n", ms.Unapplied()) + fmt.Printf("last migration group: %s\n", ms.LastGroup()) + return nil + }, + }, + { + Name: "mark_applied", + Usage: "mark migrations as applied without actually running them", + Action: func(c *cli.Context) error { + group, err := migrator.Migrate(c.Context, migrate.WithNopMigration()) + if err != nil { + return err + } + if group.IsZero() { + fmt.Printf("there are no new migrations to mark as applied\n") + return nil + } + fmt.Printf("marked as applied %s\n", group) + return nil + }, + }, + }, + } +} diff --git a/backend/cmd/migrate/migrations/20250128162856_add_users_attributes.down.sql b/backend/cmd/migrate/migrations/20250128162856_add_users_attributes.down.sql new file mode 100644 index 0000000..029e2fc --- /dev/null +++ b/backend/cmd/migrate/migrations/20250128162856_add_users_attributes.down.sql @@ -0,0 +1 @@ +ALTER TABLE users DROP COLUMN attributes; diff --git a/backend/cmd/migrate/migrations/20250128162856_add_users_attributes.up.sql b/backend/cmd/migrate/migrations/20250128162856_add_users_attributes.up.sql new file mode 100644 index 0000000..cbab347 --- /dev/null +++ b/backend/cmd/migrate/migrations/20250128162856_add_users_attributes.up.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN attributes jsonb; diff --git a/backend/cmd/migrate/migrations/main.go b/backend/cmd/migrate/migrations/main.go new file mode 100644 index 0000000..781c88d --- /dev/null +++ b/backend/cmd/migrate/migrations/main.go @@ -0,0 +1,11 @@ +package migrations + +import "github.com/uptrace/bun/migrate" + +var Migrations = migrate.NewMigrations() + +func init() { + if err := Migrations.DiscoverCaller(); err != nil { + panic(err) + } +} diff --git a/backend/go.mod b/backend/go.mod index 51ec100..48f4945 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -12,6 +12,12 @@ require ( github.com/uptrace/bun/driver/pgdriver v1.2.8 ) +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect +) + require ( github.com/gorilla/csrf v1.7.2 // direct github.com/gorilla/securecookie v1.1.2 // indirect @@ -26,6 +32,7 @@ require ( github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/uptrace/bun/extra/bundebug v1.2.8 // direct + github.com/urfave/cli/v2 v2.27.5 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/crypto v0.31.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index dcaf24f..6bc358e 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,3 +1,5 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -27,6 +29,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= @@ -39,10 +43,14 @@ github.com/uptrace/bun/driver/pgdriver v1.2.8 h1:5XrNn/9enSrWhhrUpz+6PY9S1vcg/jh github.com/uptrace/bun/driver/pgdriver v1.2.8/go.mod h1:cwRRwqabgePwYBiLlXtbeNmPD7LGJnqP21J2ZKP4ah8= github.com/uptrace/bun/extra/bundebug v1.2.8 h1:Epv0ycLOnoKWPky+rufP2F/PrcSlKkd4tmVIFOdq90A= github.com/uptrace/bun/extra/bundebug v1.2.8/go.mod h1:ucnmuPw/5ePbNFj2SPmV0lQh3ZvL+3HCrpvRxIYZyWQ= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/backend/main.go b/backend/main.go index 0613c85..37962d9 100644 --- a/backend/main.go +++ b/backend/main.go @@ -12,7 +12,12 @@ import ( _ "github.com/lib/pq" "fr.latosa-escrima/api" + "fr.latosa-escrima/api/blogs" "fr.latosa-escrima/api/core" + "fr.latosa-escrima/api/events" + "fr.latosa-escrima/api/media" + "fr.latosa-escrima/api/shortcodes" + "fr.latosa-escrima/api/users" "github.com/gorilla/csrf" ) @@ -21,22 +26,6 @@ var CORS_AllowOrigin string func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "