commit bf78edeec04c44cee655dc1e76c83a297e7c99e5 Author: Josepablo C. Date: Sun Dec 7 08:51:10 2025 -0600 initial commit, adding SQL connection and a simple testing on main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dbc2b15 --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# Binaries for programs and plugins +*.exe +*.exec +*.exe~ +*.dll +*.so +*.dylib +*.test + +# Build output (common names) +/bin/ +/dist/ +/build/ +*.out +coverage.out + +# Go tooling +go.work +# Uncomment if you don't commit vendored deps +# vendor/ + +# Dependency manager files (optional) +Gopkg.lock +glide.lock + +# Generated code +gen/ +generated/ + +# Environment / secrets +.env +.env.* +*.key +*.pem + +# Logs +*.log + +# Editor directories and files +.idea/ +.vscode/ +*.iml +*.sublime-project +*.sublime-workspace + +# Swap/backup files +*~ +*.swp +*.swo +.#* + +# macOS / Windows +.DS_Store +Thumbs.db + +# Container / OS artifacts +docker-compose.override.yml + +# CI artifacts +coverage/ +reports/ + +# Misc +node_modules/ \ No newline at end of file diff --git a/app/go.mod b/app/go.mod new file mode 100644 index 0000000..317a053 --- /dev/null +++ b/app/go.mod @@ -0,0 +1,29 @@ +module cloud.etaviaporte.com/api + +go 1.23.2 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/spf13/viper v1.21.0 + gorm.io/driver/mysql v1.6.0 + gorm.io/gorm v1.31.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.28.0 // indirect +) diff --git a/app/go.sum b/app/go.sum new file mode 100644 index 0000000..c1be72d --- /dev/null +++ b/app/go.sum @@ -0,0 +1,61 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/app/libs/database/db.go b/app/libs/database/db.go new file mode 100644 index 0000000..08c8fda --- /dev/null +++ b/app/libs/database/db.go @@ -0,0 +1,42 @@ +package database + +import ( + "log" + + "github.com/joho/godotenv" + "github.com/spf13/viper" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +var shared_db_connection *gorm.DB + +func initDB() *gorm.DB { + // load .env into environment (so viper can read them) + if err := godotenv.Load(); err != nil { + log.Println(".env not found or could not be loaded; proceeding with existing environment variables") + } + + viper.AutomaticEnv() // read from environment + + sql_dsn := viper.GetString("SQL_DSN") + + if sql_dsn == "" { + log.Fatal("SQL_DSN must be set in .env or environment") + } + + db, err := gorm.Open(mysql.Open(sql_dsn), &gorm.Config{}) + if err != nil { + log.Fatalf("failed to connect to MySQL Server: %v", err) + } + + log.Println("connected to MySQL Server") + return db +} + +func Init() *gorm.DB { + if shared_db_connection == nil { + shared_db_connection = initDB() + } + return shared_db_connection +} diff --git a/app/libs/database/schemas/rbac/rbac.go b/app/libs/database/schemas/rbac/rbac.go new file mode 100644 index 0000000..b0c5192 --- /dev/null +++ b/app/libs/database/schemas/rbac/rbac.go @@ -0,0 +1,124 @@ +/** + * @file schema.rbac.go + * @brief RBAC schema models for GORM + * + * This file defines the base database models used by the RBAC + * (Role-Based Access Control) system. Models map to the following + * tables: user_types, users, auth_identities, auth_credentials, + * roles, permissions, role_permissions and user_roles. + * + * The structs include GORM tags for column names and relationships: + * - UserType: types of users. + * - User: main user record; links to UserType, AuthIdentity and UserRole. + * - AuthIdentity: external identity providers; links to AuthCredential. + * - AuthCredential: stored credentials for an identity. + * - Role: role definitions and their permissions and assigned users. + * - Permission: permission definitions. + * - RolePermission: join table between Role and Permission. + * - UserRole: join table between User and Role with optional expiration. + * + * These models are intended for use with GORM to perform ORM operations + * against the RBAC schema. + */ + +package rbac + +import ( + "time" +) + +type UserType struct { + ID uint `gorm:"primaryKey;column:id"` + Name string `gorm:"type:text;column:name"` + Description *string `gorm:"type:text;column:description"` +} + +func (UserType) TableName() string { return "user_types" } + +type User struct { + ID uint `gorm:"primaryKey;column:id"` + UserTypeID uint `gorm:"column:user_type"` + Name string `gorm:"type:text;column:name"` + LastName string `gorm:"type:text;column:last_name"` + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` + UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"` + + UserType UserType `gorm:"foreignKey:UserTypeID;references:ID"` + AuthIdentities []AuthIdentity `gorm:"foreignKey:UserID;references:ID"` + UserRoles []UserRole `gorm:"foreignKey:UserID;references:ID"` +} + +func (User) TableName() string { return "users" } + +type AuthIdentity struct { + ID uint `gorm:"primaryKey;column:id"` + UserID uint `gorm:"column:user_id"` + Provider string `gorm:"type:text;column:provider"` + Identifier string `gorm:"type:text;column:identifier"` + IsPrimary bool `gorm:"column:is_primary"` + IsVerified bool `gorm:"column:is_verified"` + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` + UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"` + + User User `gorm:"foreignKey:UserID;references:ID"` + Credentials []AuthCredential `gorm:"foreignKey:IdentityID;references:ID"` +} + +func (AuthIdentity) TableName() string { return "auth_identities" } + +type AuthCredential struct { + ID uint `gorm:"primaryKey;column:id"` + IdentityID uint `gorm:"column:identity_id"` + Password string `gorm:"type:text;column:password"` + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` + UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"` + + Identity AuthIdentity `gorm:"foreignKey:IdentityID;references:ID"` +} + +func (AuthCredential) TableName() string { return "auth_credentials" } + +type Role struct { + ID uint `gorm:"primaryKey;column:id"` + Name string `gorm:"type:text;column:name"` + Description *string `gorm:"type:text;column:description"` + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` + UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"` + RolePermissions []RolePermission `gorm:"foreignKey:RoleID;references:ID"` + UserRoles []UserRole `gorm:"foreignKey:RoleID;references:ID"` +} + +func (Role) TableName() string { return "roles" } + +type Permission struct { + ID uint `gorm:"primaryKey;column:id"` + Name string `gorm:"type:text;column:name"` + Description *string `gorm:"type:text;column:description"` + RolePermissions []RolePermission `gorm:"foreignKey:PermissionID;references:ID"` +} + +func (Permission) TableName() string { return "permissions" } + +type RolePermission struct { + ID uint `gorm:"primaryKey;column:id"` + RoleID uint `gorm:"column:role_id"` + PermissionID uint `gorm:"column:permission_id"` + + Role Role `gorm:"foreignKey:RoleID;references:ID"` + Permission Permission `gorm:"foreignKey:PermissionID;references:ID"` +} + +func (RolePermission) TableName() string { return "role_permissions" } + +type UserRole struct { + ID uint `gorm:"primaryKey;column:id"` + UserID uint `gorm:"column:user_id"` + RoleID uint `gorm:"column:role_id"` + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` + ExpiresAt *time.Time `gorm:"column:expires_at"` + + User User `gorm:"foreignKey:UserID;references:ID"` + Role Role `gorm:"foreignKey:RoleID;references:ID"` +} + +func (UserRole) TableName() string { return "user_roles" } diff --git a/app/main.go b/app/main.go new file mode 100644 index 0000000..3340aca --- /dev/null +++ b/app/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + + database "cloud.etaviaporte.com/api/libs/database" + rbac "cloud.etaviaporte.com/api/libs/database/schemas/rbac" +) + +func main() { + var connection = database.Init() + + // Prepare a variable to hold the fetched user record. + // Replace rbac.User with the actual struct name if different. + var user rbac.User + + // Fetch the user whose primary key is 1. + // GORM's First method, when given a numeric id as the second argument, queries by primary key. + result := connection.First(&user, 1) + + // result.Error holds any error from the DB operation (including "record not found"). + // result.RowsAffected tells how many rows were returned. + if result.Error != nil { + // Handle the error (could be gorm.ErrRecordNotFound or a DB/connection error). + // For learning purposes we print the error and exit main. + fmt.Printf("failed to fetch user with id=1: %v\n", result.Error) + return + } + + // If no error, user now contains the row from the database. + // Print the struct with field names and values to inspect the loaded data. + fmt.Printf("User fetched: %+v\n", user) + +} diff --git a/app/openapi.yaml b/app/openapi.yaml new file mode 100644 index 0000000..d784ba2 --- /dev/null +++ b/app/openapi.yaml @@ -0,0 +1,182 @@ +openapi: 3.0.3 +info: + title: Authorization API + version: "1.0.0" + description: Simple authorization endpoints for login, refresh, logout and getting current user info. +servers: + - url: http://localhost:8080 + description: Local development server + +paths: + /auth/login: + post: + summary: Obtain access and refresh tokens + tags: + - Auth + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequest' + responses: + '200': + description: Tokens issued + content: + application/json: + schema: + $ref: '#/components/schemas/TokenResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + + /auth/refresh: + post: + summary: Refresh access token using a refresh token + tags: + - Auth + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RefreshRequest' + responses: + '200': + description: New tokens + content: + application/json: + schema: + $ref: '#/components/schemas/TokenResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + + /auth/logout: + post: + summary: Revoke refresh token / logout + tags: + - Auth + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RevokeRequest' + responses: + '204': + description: Successfully logged out (no content) + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + + /auth/me: + get: + summary: Get current authenticated user + tags: + - Auth + security: + - bearerAuth: [] + responses: + '200': + description: Current user profile + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfile' + '401': + $ref: '#/components/responses/Unauthorized' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + LoginRequest: + type: object + required: + - username + - password + properties: + username: + type: string + example: user@example.com + password: + type: string + format: password + example: secret123 + + TokenResponse: + type: object + properties: + accessToken: + type: string + example: eyJhbGciOi... + refreshToken: + type: string + example: dummyr3fr3sht0k3n + expiresIn: + type: integer + description: Seconds until access token expiration + example: 3600 + + RefreshRequest: + type: object + required: + - refreshToken + properties: + refreshToken: + type: string + + RevokeRequest: + type: object + required: + - refreshToken + properties: + refreshToken: + type: string + + UserProfile: + type: object + properties: + id: + type: string + example: "123e4567-e89b-12d3-a456-426614174000" + username: + type: string + example: user@example.com + email: + type: string + example: user@example.com + + Error: + type: object + properties: + code: + type: string + example: invalid_request + message: + type: string + example: "Detailed error message" + + responses: + BadRequest: + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Unauthorized: + description: Authentication failed or missing credentials + content: + application/json: + schema: + $ref: '#/components/schemas/Error' diff --git a/db/.gitignore b/db/.gitignore new file mode 100644 index 0000000..751553b --- /dev/null +++ b/db/.gitignore @@ -0,0 +1 @@ +*.bak diff --git a/db/Dockerfile b/db/Dockerfile new file mode 100644 index 0000000..4a03601 --- /dev/null +++ b/db/Dockerfile @@ -0,0 +1,23 @@ +# Original author : https://github.com/yobasystems/alpine-mariadb.git +FROM alpine:latest + +ARG BUILD_DATE +ARG VCS_REF + +RUN apk add --no-cache mariadb mysql-client mariadb-server-utils pwgen && \ + rm -f /var/cache/apk/* + +RUN mkdir /schemas +COPY schemas /schemas + +ADD scripts/run.sh /scripts/run.sh +RUN mkdir /docker-entrypoint-initdb.d && \ + mkdir /scripts/pre-exec.d && \ + mkdir /scripts/pre-init.d && \ + chmod -R 755 /scripts + +EXPOSE 3306 + +VOLUME ["/var/lib/mysql"] + +ENTRYPOINT ["/scripts/run.sh"] diff --git a/db/Models/assets/RBAC_model.svg b/db/Models/assets/RBAC_model.svg new file mode 100644 index 0000000..2f7c5d1 --- /dev/null +++ b/db/Models/assets/RBAC_model.svg @@ -0,0 +1,1474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/db/Models/model.mwb b/db/Models/model.mwb new file mode 100644 index 0000000..f217027 Binary files /dev/null and b/db/Models/model.mwb differ diff --git a/db/Models/sql_generated/db_init.sql b/db/Models/sql_generated/db_init.sql new file mode 100644 index 0000000..8daa7ce --- /dev/null +++ b/db/Models/sql_generated/db_init.sql @@ -0,0 +1,55 @@ +-- Creation of basic User Types +INSERT INTO user_types (name, description) VALUES ('root','An easy way to identify the root user of the system'); +INSERT INTO user_types (name, description) VALUES ('company_owner','Company Owner'); +INSERT INTO user_types (name, description) VALUES ('company_manager','Company Level manager'); +INSERT INTO user_types (name, description) VALUES ('company_staff','Company Level Staff Memeber'); +INSERT INTO user_types (name, description) VALUES ('company_driver','Company Level Driver Member'); +INSERT INTO user_types (name, description) VALUES ('company_observer','Company Level Driver Member'); + +-- Creation of first user +INSERT INTO users (user_type,name,last_name,created_at,updated_at) VALUES (1,'Pablo','Cruz',NOW(),NOW()); -- root -> root + +-- Creation of basic roles +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("root","Root role with no restricted access",NOW(),NOW()); +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("system_admin","System Level Admin",NOW(),NOW()); +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("system_developer","System Level Developer",NOW(),NOW()); +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("system_reader","System Level Read Only",NOW(),NOW()); +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("system_staff","System Level staff member",NOW(),NOW()); +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("company_owner","Unrestricted access to company resources",NOW(),NOW()); +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("company_manager","Access as manager to company resources",NOW(),NOW()); +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("company_staff","Access as staff to company resources",NOW(),NOW()); +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("company_driver","Simple access to company resources",NOW(),NOW()); +INSERT INTO roles (name, description, created_at, updated_at) VALUES ("company_observer","Limited access to company resources",NOW(),NOW()); + +-- Creation of basic permissions +INSERT INTO permissions (name, description) VALUES ("root","Root role with no restricted access"); +INSERT INTO permissions (name, description) VALUES ("system.admin","System Level Admin"); +INSERT INTO permissions (name, description) VALUES ("system.developer","System Level Developer"); +INSERT INTO permissions (name, description) VALUES ("system.reader","System Level Reader"); +INSERT INTO permissions (name, description) VALUES ("system.staff","System Level Staff Member"); +INSERT INTO permissions (name, description) VALUES ("company.owner","Unrestricted access to company resources"); +INSERT INTO permissions (name, description) VALUES ("company.manager","Admin access to company resources"); +INSERT INTO permissions (name, description) VALUES ("company.staff","Staff access to company resources"); +INSERT INTO permissions (name, description) VALUES ("company.driver","Driver with simple access to company resources"); +INSERT INTO permissions (name, description) VALUES ("company.observer","Observer with simple access to company resources"); + +-- Link of basic roles with its permissions +INSERT INTO role_permissions (role_id, permission_id) VALUES( 1, 1 ); -- root -> root +INSERT INTO role_permissions (role_id, permission_id) VALUES( 2, 2 ); +INSERT INTO role_permissions (role_id, permission_id) VALUES( 3, 3 ); +INSERT INTO role_permissions (role_id, permission_id) VALUES( 4, 4 ); +INSERT INTO role_permissions (role_id, permission_id) VALUES( 5, 5 ); +INSERT INTO role_permissions (role_id, permission_id) VALUES( 6, 6 ); +INSERT INTO role_permissions (role_id, permission_id) VALUES( 7, 7 ); +INSERT INTO role_permissions (role_id, permission_id) VALUES( 8, 8 ); +INSERT INTO role_permissions (role_id, permission_id) VALUES( 9, 9 ); +INSERT INTO role_permissions (role_id, permission_id) VALUES( 10, 10 ); + +-- Link of Root User with its role + +INSERT INTO user_roles (user_id, role_id, created_at ) VALUES (1,1,NOW()); -- root -> root + +-- Create Root Auth Identity and credentials + +INSERT INTO auth_identities (user_id, provider, identifier, is_primary, is_verified, created_at, updated_at ) VALUES (1,"email","josepablo134@gmail.com",1,1,NOW(),NOW()); -- root access with email +INSERT INTO auth_credentials (identity_id, password, created_at, updated_at) VALUES (1, "PasswordGoesHere", NOW(), NOW()); diff --git a/db/Models/sql_generated/schema.sql b/db/Models/sql_generated/schema.sql new file mode 100644 index 0000000..d0dde57 --- /dev/null +++ b/db/Models/sql_generated/schema.sql @@ -0,0 +1,174 @@ +-- MySQL Script generated by MySQL Workbench +-- Sun Dec 7 08:38:42 2025 +-- Model: New Model Version: 1.0 +-- MySQL Workbench Forward Engineering + +SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; + +-- ----------------------------------------------------- +-- Schema u947463964_etaviaporte +-- ----------------------------------------------------- + +-- ----------------------------------------------------- +-- Schema u947463964_etaviaporte +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS `u947463964_etaviaporte` DEFAULT CHARACTER SET utf8 ; +USE `u947463964_etaviaporte` ; + +-- ----------------------------------------------------- +-- Table `u947463964_etaviaporte`.`user_types` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `u947463964_etaviaporte`.`user_types` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` TEXT NOT NULL, + `description` TEXT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `u947463964_etaviaporte`.`users` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `u947463964_etaviaporte`.`users` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `user_type` INT UNSIGNED NOT NULL, + `name` TEXT NOT NULL, + `last_name` TEXT NOT NULL, + `created_at` DATETIME NOT NULL, + `updated_at` DATETIME NOT NULL, + UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE, + PRIMARY KEY (`id`), + INDEX `fk_users_user_types1_idx` (`user_type` ASC) VISIBLE, + CONSTRAINT `fk_users_user_types1` + FOREIGN KEY (`user_type`) + REFERENCES `u947463964_etaviaporte`.`user_types` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `u947463964_etaviaporte`.`auth_identities` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `u947463964_etaviaporte`.`auth_identities` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `user_id` INT UNSIGNED NOT NULL, + `provider` TEXT NOT NULL, + `identifier` TEXT NOT NULL COMMENT 'email, phone google, facebook, etc.', + `is_primary` TINYINT NOT NULL DEFAULT 0, + `is_verified` TINYINT NOT NULL DEFAULT 0, + `created_at` DATETIME NOT NULL, + `updated_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE, + INDEX `fk_auth_identities_users_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_auth_identities_users` + FOREIGN KEY (`user_id`) + REFERENCES `u947463964_etaviaporte`.`users` (`id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `u947463964_etaviaporte`.`auth_credentials` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `u947463964_etaviaporte`.`auth_credentials` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `identity_id` INT UNSIGNED NOT NULL, + `password` TEXT NOT NULL, + `created_at` DATETIME NOT NULL, + `updated_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE, + INDEX `fk_auth_credentials_auth_identities1_idx` (`identity_id` ASC) VISIBLE, + CONSTRAINT `fk_auth_credentials_auth_identities1` + FOREIGN KEY (`identity_id`) + REFERENCES `u947463964_etaviaporte`.`auth_identities` (`id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `u947463964_etaviaporte`.`roles` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `u947463964_etaviaporte`.`roles` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` TEXT NOT NULL, + `description` TEXT NULL, + `created_at` DATETIME NOT NULL, + `updated_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `u947463964_etaviaporte`.`permissions` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `u947463964_etaviaporte`.`permissions` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` TEXT NOT NULL, + `description` TEXT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `u947463964_etaviaporte`.`role_permissions` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `u947463964_etaviaporte`.`role_permissions` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `role_id` INT UNSIGNED NOT NULL, + `permission_id` INT UNSIGNED NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE, + INDEX `fk_role_permissions_roles1_idx` (`role_id` ASC) VISIBLE, + INDEX `fk_role_permissions_permissions1_idx` (`permission_id` ASC) VISIBLE, + CONSTRAINT `fk_role_permissions_roles1` + FOREIGN KEY (`role_id`) + REFERENCES `u947463964_etaviaporte`.`roles` (`id`) + ON DELETE CASCADE + ON UPDATE NO ACTION, + CONSTRAINT `fk_role_permissions_permissions1` + FOREIGN KEY (`permission_id`) + REFERENCES `u947463964_etaviaporte`.`permissions` (`id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `u947463964_etaviaporte`.`user_roles` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `u947463964_etaviaporte`.`user_roles` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `user_id` INT UNSIGNED NOT NULL, + `role_id` INT UNSIGNED NOT NULL, + `created_at` DATETIME NOT NULL, + `expires_at` DATETIME NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE, + INDEX `fk_user_roles_users1_idx` (`user_id` ASC) VISIBLE, + INDEX `fk_user_roles_roles1_idx` (`role_id` ASC) VISIBLE, + CONSTRAINT `fk_user_roles_users1` + FOREIGN KEY (`user_id`) + REFERENCES `u947463964_etaviaporte`.`users` (`id`) + ON DELETE CASCADE + ON UPDATE NO ACTION, + CONSTRAINT `fk_user_roles_roles1` + FOREIGN KEY (`role_id`) + REFERENCES `u947463964_etaviaporte`.`roles` (`id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/db/README.md b/db/README.md new file mode 100644 index 0000000..26ada2f --- /dev/null +++ b/db/README.md @@ -0,0 +1,20 @@ +# ETAv2SQL + +# MySQL Databse schema + +![etaviaporte](./Models/assets/Modelv2.0.0.png) + +# Docker + +## Build Docker image + +docker buildx build -t eta/eta-db + +## Run Docker container + +```{.sh} +docker run --name eta-db -d -p3306:3306 eta/eta-db +docker logs -f eta-db +``` + +Container will generate automatically a SQL root password diff --git a/db/scripts/migrate.sh b/db/scripts/migrate.sh new file mode 100644 index 0000000..bf512ac --- /dev/null +++ b/db/scripts/migrate.sh @@ -0,0 +1,12 @@ +#! /bin/bash + +# This script does not avoid invalid queries while applying changes +USER=developer +HOST=127.0.0.2 +PASS=Password1234 +DBNAME=etaviaporte + +mysqldump -h$HOST -u$USER -p$PASS $DBNAME > temp_copy.sql + +mysql -h$HOST -u$USER -p$PASS < model.sql +mysql -h$HOST -u$USER -p$PASS < model_init.sql diff --git a/db/scripts/run.sh b/db/scripts/run.sh new file mode 100644 index 0000000..cc5b9e9 --- /dev/null +++ b/db/scripts/run.sh @@ -0,0 +1,111 @@ +#!/bin/sh + +# execute any pre-init scripts +for i in /scripts/pre-init.d/*sh +do + if [ -e "${i}" ]; then + echo "[i] pre-init.d - processing $i" + . "${i}" + fi +done + +if [ -d "/run/mysqld" ]; then + echo "[i] mysqld already present, skipping creation" + chown -R mysql:mysql /run/mysqld +else + echo "[i] mysqld not found, creating...." + mkdir -p /run/mysqld + chown -R mysql:mysql /run/mysqld +fi + +if [ -d /var/lib/mysql/mysql ]; then + echo "[i] MySQL directory already present, skipping creation" + chown -R mysql:mysql /var/lib/mysql + + echo "[i] MySQL root Password:" $(cat /schemas/password.txt) +else + echo "[i] MySQL data directory not found, creating initial DBs" + + chown -R mysql:mysql /var/lib/mysql + + mysql_install_db --user=mysql --ldata=/var/lib/mysql > /dev/null + + if [ "$MYSQL_ROOT_PASSWORD" = "" ]; then + MYSQL_ROOT_PASSWORD=`pwgen 16 1` + echo "[i] MySQL root Password: $MYSQL_ROOT_PASSWORD" + fi + + MYSQL_DATABASE=${MYSQL_DATABASE:-""} + MYSQL_USER=${MYSQL_USER:-""} + MYSQL_PASSWORD=${MYSQL_PASSWORD:-""} + + tfile=`mktemp` + if [ ! -f "$tfile" ]; then + return 1 + fi + + cat << EOF > $tfile +USE mysql; +FLUSH PRIVILEGES ; +GRANT ALL ON *.* TO 'root'@'%' identified by '$MYSQL_ROOT_PASSWORD' WITH GRANT OPTION ; +GRANT ALL ON *.* TO 'root'@'localhost' identified by '$MYSQL_ROOT_PASSWORD' WITH GRANT OPTION ; +SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ; +DROP DATABASE IF EXISTS test ; +FLUSH PRIVILEGES ; +EOF + + if [ "$MYSQL_DATABASE" != "" ]; then + echo "[i] Creating database: $MYSQL_DATABASE" + if [ "$MYSQL_CHARSET" != "" ] && [ "$MYSQL_COLLATION" != "" ]; then + echo "[i] with character set [$MYSQL_CHARSET] and collation [$MYSQL_COLLATION]" + echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` CHARACTER SET $MYSQL_CHARSET COLLATE $MYSQL_COLLATION;" >> $tfile + else + echo "[i] with character set: 'utf8' and collation: 'utf8_general_ci'" + echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` CHARACTER SET utf8 COLLATE utf8_general_ci;" >> $tfile + fi + + if [ "$MYSQL_USER" != "" ]; then + echo "[i] Creating user: $MYSQL_USER with password $MYSQL_PASSWORD" + echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* to '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD';" >> $tfile + fi + fi + + /usr/bin/mysqld --user=mysql --bootstrap --verbose=0 --skip-name-resolve --skip-networking=0 < $tfile + rm -f $tfile + + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sql) echo "$0: running $f"; /usr/bin/mysqld --user=mysql --bootstrap --verbose=0 --skip-name-resolve --skip-networking=0 < "$f"; echo ;; + *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | /usr/bin/mysqld --user=mysql --bootstrap --verbose=0 --skip-name-resolve --skip-networking=0 < "$f"; echo ;; + *) echo "$0: ignoring or entrypoint initdb empty $f" ;; + esac + echo + done + + echo + echo 'MySQL init process done. Ready for start up.' + echo + + echo "exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0" "$@" + + # Saving plain password + echo $MYSQL_ROOT_PASSWORD > /schemas/password.txt + + # # After 5 seconds (once mysql is up) create schema. + # $(sleep 5 && /usr/bin/mysql -uroot -p$MYSQL_ROOT_PASSWORD < /schemas/wincify.sql &&\ + # /usr/bin/mysql -uroot -p$MYSQL_ROOT_PASSWORD < /schemas/wincify_zones.sql &&\ + # /usr/bin/mysql -uroot -p$MYSQL_ROOT_PASSWORD < /schemas/wincify_categories.sql &&\ + # /usr/bin/mysql -uroot -p$MYSQL_ROOT_PASSWORD < /schemas/system.sql &&\ + # /usr/bin/mysql -uroot -p$MYSQL_ROOT_PASSWORD wincify_system < /schemas/wincifySystem_init.sql ) &\ +fi + +# execute any pre-exec scripts +for i in /scripts/pre-exec.d/*sh +do + if [ -e "${i}" ]; then + echo "[i] pre-exec.d - processing $i" + . ${i} + fi +done + +exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0 $@