├── .env ├── .gitignore ├── Dockerfile ├── README.md ├── _database └── golang_fiber_mvc.sql ├── _files ├── other.csv ├── tasks_for_upload.csv └── wrong_file.csv ├── config ├── app_config.go ├── db_connection.go ├── env_config.go ├── log_config.go ├── mail_config.go └── session_config.go ├── controllers ├── auth_controller.go ├── back_controller.go ├── front_controller.go ├── helper_controller.go ├── interface_controller.go ├── middleware.go ├── report_controller.go ├── setup.go ├── statistic_controller.go ├── task_controller.go └── user_controller.go ├── docker-compose.yml ├── docs ├── Add-Attahment.jpg ├── Add-Task.jpg ├── Add-User.jpg ├── Architecture.jpg ├── Design.jpg ├── Edit-Task.jpg ├── Home-Admin.jpg ├── Reports.jpg ├── Search-Tasks.jpg ├── Task-Details.jpg ├── Task-Report.jpg ├── Tasks.jpg ├── User-Statistics.jpg └── Users.jpg ├── entities ├── role.go ├── setup.go ├── task.go ├── task_complexity.go ├── task_status.go └── user.go ├── go.mod ├── go.sum ├── helpers ├── constants.go ├── email_service.go ├── file_uploader.go ├── functions.go ├── html_pdf_generator.go ├── pagination.go ├── password_hashing.go └── time_date.go ├── logs ├── auth-logs.log ├── report-logs.log ├── task-logs.log └── user-logs.log ├── main.go ├── models ├── csv_parser_task.go ├── report_model.go ├── role_model.go ├── statistic_model.go ├── task_complexity_model.go ├── task_model.go ├── task_status_model.go └── user_model.go ├── setup └── create_user.go ├── static ├── css │ ├── bootstrap.min.css │ └── signin.css ├── images │ ├── blank-user.png │ └── golang-logo.png ├── js │ ├── bootstrap.min.js │ ├── bootstrap.min.js.map │ └── jquery-3.6.1.min.js └── uploads │ └── imgs │ ├── 1695205811266044400.jpg │ ├── 1695807195804061300.png │ └── 1695810331709409800.png └── templates ├── auth ├── get-recovery-link.html ├── login.html ├── recover-password.html └── recovery-link.html ├── back-office ├── 404-home.html ├── home-admin.html ├── home-normal.html ├── home.html └── user-data.html ├── error ├── authentication.html └── pagination.html ├── front-office ├── about.html └── index.html ├── partials ├── 404-menu.html ├── footer-auth.html ├── footer-back.html ├── header-auth.html ├── header-back.html ├── menu-admin.html ├── menu-normal.html └── menu.html ├── reports ├── blocked-tasks.html ├── canceled-tasks.html ├── completed-tasks.html ├── in-progress-tasks.html ├── index.html ├── pending-tasks.html ├── statistics.html ├── tasks.html └── users.html ├── statistic └── index.html ├── task ├── add-attachment.html ├── add.html ├── details.html ├── edit.html ├── index.html ├── my-tasks.html ├── search-results.html ├── search.html └── upload-csv.html └── user ├── activate.html ├── add-image.html ├── add.html ├── deactivate.html ├── details.html ├── edit.html ├── index.html ├── search-results.html └── search.html /.env: -------------------------------------------------------------------------------- 1 | # Application settings 2 | APP_PORT=5000 3 | APP_HOST=0.0.0.0 4 | APP_SESSION_EXPIRATION=2 # Minutes 5 | APP_ITEMS_PER_PAGE=10 # 6 | 7 | # Database settings 8 | DATABASE_URL=root:003334743LA032@tcp(localhost:3306)/golang_fiber_mvc?charset=utf8mb4&parseTime=True&loc=Local 9 | 10 | # File upload paths 11 | UPLOAD_IMAGE_PATH=./static/uploads/imgs 12 | UPLOAD_DOCUMENT_PATH=./static/uploads/docs 13 | 14 | # Logging settings 15 | LOG_ROOT_PATH=./logs 16 | LOG_MAX_SIZE=100 # MB 17 | LOG_MAX_AGE=24 #Days 18 | LOG_MAX_BACKUPS=3 19 | 20 | # Email settings 21 | MAIL_SMTP_HOST="smtp.gmail.com" 22 | MAIL_SMTP_PORT=587 23 | MAIL_USER="@gmail.com" 24 | MAIL_PASSWORD="" 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.pdf 3 | *.log -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM golang:alpine as builder 3 | WORKDIR /app 4 | COPY go.mod . 5 | COPY go.sum . 6 | RUN go mod download 7 | COPY . . 8 | RUN GOOS=linux go build -o golang-fiber-webapp 9 | 10 | # small image 11 | FROM alpine 12 | WORKDIR /app 13 | COPY --from=builder /app /app/ 14 | EXPOSE 5000 15 | CMD [ "./golang-fiber-webapp" ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang App - Fiber Framework 2 | 3 | ## Application - Task Management 4 | - [x] Create Users 5 | - [x] Upload User image 6 | - [x] Add Tasks 7 | - [x] Upload Tasks from CSV File 8 | - [x] Add Task Attachment 9 | - [x] Search Users and Tasks 10 | - [x] Reports (PDF) 11 | - [x] Statistics (Count Records) 12 | - [x] Logs 13 | - [x] Pagination 14 | 15 | ## Architecture 16 | ![Architecture](docs/Architecture.jpg) 17 | 18 | - Config 19 | - Helpers: functions (Date, Strings, Conversions, Uploads, ...) 20 | - Entities: Represents a table ina database 21 | - Models: Represents the Business Logic 22 | - Controllers: Represents all controllers or handlers 23 | - Templates: All Html templates for rendering data 24 | - Application: Main Layer that call all other layers 25 | 26 | 27 | ## Design 28 | ![Design](docs/Design.jpg) 29 | 30 | - Models: Operations: Create, Read, Find, ... 31 | - Interface for Controllers 32 | - SetupController: Load all routes 33 | - Configs: Env, Logs, Template Engines 34 | - Main: Loadd all components to run application 35 | - App: Main class or file, that call all controllers, migrations, configs and template engines 36 | 37 | ## Libraries 38 | - [GORM](https://gorm.io) 39 | - [Fiber Framework](https://github.com/gofiber/fiber) 40 | - [go-wkhtmltopdf](https://github.com/SebastiaanKlippert/go-wkhtmltopdf) - For PDF Rendering 41 | 42 | ## Installation 43 | - Create database 44 | `CREATE DATABASE golang_fiber_mvc; USE golang_fiber_mvc;` 45 | - Copy database script to MySQL Server: [golang_fiber_mvc.sql](database/golang_fiber_mvc.sql) 46 | - Download [wkhtmltopdf](https://wkhtmltopdf.org/downloads.html) 47 | - Install *wkhtmltopdf* and add to environment variables 48 | - clone git repository: ``git clone https://github.com/ortizdavid/golang-fiber-webapp`` 49 | - Install dependencies: ``go mod install`` 50 | - Create Admin User: ``go run create_user.go`` 51 | - Run application: ``go run main.go`` 52 | 53 | ## Application Screens 54 | 55 | ![Home - Admin](docs/Home-Admin.jpg) 56 | 57 | ![Users](docs/Users.jpg) 58 | 59 | ![Add Users](docs/Add-User.jpg) 60 | 61 | ![Add Task](docs/Add-Task.jpg) 62 | 63 | ![Tasks](docs/Tasks.jpg) 64 | 65 | ![Task Details](docs/Task-Details.jpg) 66 | 67 | ![Add Attachment](docs/Add-Attahment.jpg) 68 | 69 | ![Search Tasks](docs/Search-Tasks.jpg) 70 | 71 | ![User Statistics](docs/User-Statistics.jpg) 72 | 73 | ![Reports](docs/Reports.jpg) 74 | 75 | ![Task Report](docs/Task-Report.jpg) -------------------------------------------------------------------------------- /_database/golang_fiber_mvc.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS fiber_storage; 2 | CREATE TABLE IF NOT EXISTS fiber_storage ( 3 | k varchar(64) NOT NULL DEFAULT '', 4 | v blob NOT NULL, 5 | e bigint NOT NULL DEFAULT '0', 6 | PRIMARY KEY (k) 7 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; 8 | 9 | 10 | INSERT INTO fiber_storage (k, v, e) VALUES 11 | ('033b50c0-13cd-486f-a9bc-4140a7f4f582', 0x0d7f040102ff8000010c0110000075ff80000208757365726e616d6506737472696e670c0f000d7465737440757365722e636f6d0870617373776f726406737472696e670c3e003c24326124313024416c51553943363465516769584754636e322f674c75737a4a5766773331566b506b50345449364f70674b6a6d7a53543668312f61, 1700575203); 12 | 13 | DROP TABLE IF EXISTS roles; 14 | CREATE TABLE IF NOT EXISTS roles ( 15 | role_id int NOT NULL AUTO_INCREMENT, 16 | role_name varchar(100) NOT NULL, 17 | code varchar(20) NOT NULL, 18 | PRIMARY KEY (role_id), 19 | KEY idx_role_name (role_name), 20 | KEY idx_role_code (code) 21 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 22 | 23 | 24 | INSERT INTO roles (role_id, role_name, code) VALUES 25 | (1, 'Administrator', 'admin'), 26 | (2, 'Normal User', 'normal'); 27 | 28 | 29 | DROP TABLE IF EXISTS tasks; 30 | CREATE TABLE IF NOT EXISTS tasks ( 31 | task_id int NOT NULL AUTO_INCREMENT, 32 | user_id int NOT NULL, 33 | status_id int NOT NULL, 34 | complexity_id int NOT NULL, 35 | task_name varchar(100) NOT NULL, 36 | description varchar(200) NOT NULL, 37 | start_date date DEFAULT NULL, 38 | end_date date DEFAULT NULL, 39 | attachment varchar(100) DEFAULT NULL, 40 | unique_id varchar(50) DEFAULT NULL, 41 | created_at datetime DEFAULT CURRENT_TIMESTAMP, 42 | updated_at datetime DEFAULT CURRENT_TIMESTAMP, 43 | PRIMARY KEY (task_id), 44 | UNIQUE KEY unique_id (unique_id), 45 | KEY fk_task_user (user_id), 46 | KEY fk_task_status (status_id), 47 | KEY fk_task_complexity (complexity_id), 48 | KEY idx_task_name (task_name), 49 | KEY idx_start_end_date (start_date,end_date) 50 | ); 51 | 52 | 53 | INSERT INTO tasks (task_id, user_id, status_id, complexity_id, task_name, description, start_date, end_date, attachment, unique_id, created_at, updated_at) VALUES 54 | (1, 9, 1, 1, 'Testing App', 'aaa', '2023-11-10', '2023-11-16', '', '91eb19e1-4171-41ad-aec2-57822d43a253', '2023-11-20 15:00:23', '2023-11-20 15:00:23'), 55 | (4, 9, 1, 2, 'Upload Code to Github', 'Uploading code on Github', '2023-01-01', '2023-02-04', '', '9e207f57-4bf2-41ed-84d9-ac6a411cf33b', '2023-11-20 15:58:21', '2023-11-20 15:58:21'), 56 | (5, 9, 1, 2, 'Upload Code to Github', 'Uploading code on Github', '2023-01-01', '2023-02-04', '', 'bf763e27-4738-40f1-87c3-ac28d7a8d3c0', '2023-11-20 15:58:21', '2023-11-20 15:58:21'), 57 | (6, 9, 1, 2, 'Upload Code to Github', 'Uploading code on Github', '2023-01-01', '2023-02-04', '', '585ee826-e349-47a5-927e-9c0c8e7dd617', '2023-11-20 15:58:21', '2023-11-20 15:58:21'), 58 | (7, 9, 1, 2, 'Upload Code to Github', 'Uploading code on Github', '2023-01-01', '2023-02-04', '', '4021432e-cb3f-40ec-ab56-5c6541a70a33', '2023-11-20 15:58:21', '2023-11-20 15:58:21'), 59 | (8, 9, 1, 2, 'Upload Code to Github', 'Uploading code on Github', '2023-01-01', '2023-02-04', '', '37fd3123-5ab7-418e-a3e4-70cee78e1c5c', '2023-11-20 15:58:21', '2023-11-20 15:58:21'), 60 | (9, 9, 1, 2, 'Upload Code to Github', 'Uploading code on Github', '2023-01-01', '2023-02-04', '', '3b109ee6-73c6-466d-9695-493d35e22ea3', '2023-11-20 15:58:21', '2023-11-20 15:58:21'), 61 | (10, 9, 1, 2, 'Upload Code to Github', 'Uploading code on Github', '2023-01-01', '2023-02-04', '', 'c2e71b78-eb39-470b-a5cc-439b9a024528', '2023-11-20 15:58:21', '2023-11-20 15:58:21'), 62 | (11, 9, 1, 2, 'Upload Code to Github', 'Uploading code on Github', '2023-01-01', '2023-02-04', '', '277052a0-5178-4a3b-80cd-ee29977e9dcb', '2023-11-20 15:58:21', '2023-11-20 15:58:21'), 63 | (12, 9, 1, 2, 'Upload Code to Github', 'Uploading code on Github', '2023-01-01', '2023-02-04', '', '43c635ad-fa23-4908-916b-6fba745251be', '2023-11-20 15:58:21', '2023-11-20 15:58:21'); 64 | 65 | 66 | DROP TABLE IF EXISTS task_complexity; 67 | CREATE TABLE IF NOT EXISTS task_complexity ( 68 | complexity_id int NOT NULL AUTO_INCREMENT, 69 | complexity_name varchar(100) NOT NULL, 70 | code varchar(20) NOT NULL, 71 | PRIMARY KEY (complexity_id), 72 | KEY idx_complexity_name (complexity_name), 73 | KEY idx_status_ode (code) 74 | ); 75 | 76 | 77 | INSERT INTO task_complexity (complexity_id, complexity_name, code) VALUES 78 | (1, 'Easy', 'easy'), 79 | (2, 'Medium', 'medium'), 80 | (3, 'Hard', 'Hard'), 81 | (4, 'Very Hard', 'very-hard'), 82 | (5, 'Extremely Hard', 'extreme-hard'); 83 | 84 | 85 | 86 | DROP TABLE IF EXISTS task_status; 87 | CREATE TABLE IF NOT EXISTS task_status ( 88 | status_id int NOT NULL AUTO_INCREMENT, 89 | status_name varchar(100) NOT NULL, 90 | code varchar(20) NOT NULL, 91 | PRIMARY KEY (status_id), 92 | KEY idx_status_name (status_name), 93 | KEY idx_status_code (code) 94 | ); 95 | 96 | 97 | INSERT INTO task_status (status_id, status_name, code) VALUES 98 | (1, 'Pending', 'pending'), 99 | (2, 'In Progress', 'in-progress'), 100 | (3, 'Complete', 'complete'), 101 | (4, 'Blocked', 'blocked'), 102 | (5, 'Canceled', 'canceled'); 103 | 104 | 105 | DROP TABLE IF EXISTS users; 106 | CREATE TABLE IF NOT EXISTS users ( 107 | user_id int NOT NULL AUTO_INCREMENT, 108 | role_id int NOT NULL, 109 | user_name varchar(100) NOT NULL, 110 | password varchar(150) NOT NULL, 111 | image varchar(100) DEFAULT NULL, 112 | active enum('Yes','No') DEFAULT 'Yes', 113 | unique_id varchar(50) DEFAULT NULL, 114 | created_at datetime DEFAULT CURRENT_TIMESTAMP, 115 | updated_at datetime DEFAULT CURRENT_TIMESTAMP, 116 | token varchar(150) DEFAULT NULL, 117 | PRIMARY KEY (user_id), 118 | UNIQUE KEY unique_id (unique_id), 119 | KEY user_role (role_id), 120 | KEY idx_user_name (user_name), 121 | KEY idx_active (active) 122 | ) ; 123 | 124 | 125 | INSERT INTO users (user_id, role_id, user_name, password, image, active, unique_id, created_at, updated_at, token) VALUES 126 | (7, 1, 'admin@gmail.com', '$2a$10$WK73KU34gno.h1TqJFLrmux5uVIrNwS5TfgKxLcKxeSO15DP.McwO', '', 'Yes', '01511ee6-c7f9-4d9d-8026-0ddc8c114369', '2023-11-20 14:53:01', '2023-11-20 14:59:21', 'wBEO2VSYUQ--1kAzI3pz_vlLz9vPjAS8YTqVU6Qlh4ph2KnC7Rzy0pM192fwJyAxYt9Ypn-m_M8YojOWXGINvoqfgsT5xWYzRjTlhGgRosMoq90llyPr6dUu3KjX5kNbYRbXPw'), 127 | (8, 2, 'normal@gmail.com', '$2a$10$Rb44LaGqdM9R4Lx3zg59Z.bZGAlP05OGU5cR9Vni7W35EksJOuW/a', '', 'Yes', 'e9dce919-35d9-4e5e-8486-612d82a2882f', '2023-11-20 14:55:39', '2023-11-20 14:58:53', 'hxlzIVixinijQwgVioq94oc-4DKvSM-ZolJx7rq3VquLHqgEoBXvSTpUDJTQ0VJSSSupvLqSmDmykxRkgGkkHLkOTRXaaJTXSBU0aI5mWfcHAr1_ondZ_aG1H6ohZ3OsefrU1g'), 128 | (9, 2, 'test@user.com', '$2a$10$AlQU9C64eQgiXGTcn2/gLuszJWfw31VkPkP4TI6OpgKjmzST6h1/a', '', 'Yes', '466a518a-0630-486e-97fb-96581fdfd85e', '2023-11-20 14:59:51', '2023-11-20 15:00:03', 'TfqOwz8uCmyx0YxqIea249gJtkvL3qb2McjXwCjZq1ArSxU8zbCqR0h5bdHTJpmy-V3MWtxeKAYeACAVgPbUtPQ0MKe3M-Pug5rTAR9qYDe_TOGPKQ2LvTXB0yTAyF5ukIi5yA'); 129 | 130 | 131 | ALTER TABLE tasks 132 | ADD CONSTRAINT fk_task_complexity FOREIGN KEY (complexity_id) REFERENCES task_complexity (complexity_id), 133 | ADD CONSTRAINT fk_task_status FOREIGN KEY (status_id) REFERENCES task_status (status_id), 134 | ADD CONSTRAINT fk_task_user FOREIGN KEY (user_id) REFERENCES users (user_id); 135 | 136 | ALTER TABLE users 137 | ADD CONSTRAINT user_role FOREIGN KEY (role_id) REFERENCES roles (role_id); 138 | COMMIT; 139 | 140 | 141 | DROP VIEW IF EXISTS view_task_data; 142 | CREATE VIEW view_task_data AS 143 | SELECT 144 | ta.task_id AS task_id, 145 | ta.unique_id AS unique_id, 146 | ta.task_name AS task_name, 147 | ta.description AS description, 148 | DATE_FORMAT(ta.start_date, '%Y-%m-%d') AS start_date, 149 | DATE_FORMAT(ta.end_date, '%Y-%m-%d') AS end_date, 150 | ta.attachment AS attachment, 151 | DATE_FORMAT(ta.created_at, '%Y-%m-%d %H:%i:%s') AS created_at, 152 | DATE_FORMAT(ta.updated_at, '%Y-%m-%d %H:%i:%s') AS updated_at, 153 | us.user_id AS user_id, 154 | us.user_name AS user_name, 155 | st.status_id AS status_id, 156 | st.status_name AS status_name, 157 | st.code AS status_code, 158 | co.complexity_id AS complexity_id, 159 | co.complexity_name AS complexity_name 160 | FROM 161 | tasks ta 162 | JOIN users us ON (us.user_id = ta.user_id) 163 | JOIN task_status st ON (st.status_id = ta.status_id) 164 | JOIN task_complexity co ON (co.complexity_id = ta.complexity_id) 165 | ORDER BY ta.created_at DESC; 166 | 167 | 168 | DROP VIEW IF EXISTS view_user_data; 169 | CREATE VIEW view_user_data AS 170 | SELECT 171 | us.user_id AS user_id, 172 | us.unique_id AS unique_id, 173 | us.user_name AS user_name, 174 | us.password AS password, 175 | us.active AS active, 176 | us.image AS image, 177 | DATE_FORMAT(us.created_at, '%Y-%m-%d %H:%i:%s') AS created_at, 178 | DATE_FORMAT(us.updated_at, '%Y-%m-%d %H:%i:%s') AS updated_at, 179 | ro.role_id AS role_id, 180 | ro.role_name AS role_name, 181 | ro.code AS role_code 182 | FROM 183 | users us 184 | JOIN roles ro ON (ro.role_id = us.role_id) 185 | ORDER BY us.created_at DESC; 186 | -------------------------------------------------------------------------------- /_files/other.csv: -------------------------------------------------------------------------------- 1 | status_id,complexity_id,task_name,start_date,end_date,description 2 | 1,2,Upload Code to Github,2023-01-01,2023-02-04,Uploading code on Github 3 | 2,1,Fix Bugs in Backend,2023-02-05,2023-02-15,Resolve backend issues 4 | 3,3,Design UI for Dashboard,2023-02-16,2023-03-01,Create user-friendly UI 5 | 4,2,Optimize Database Queries,2023-03-02,2023-03-15,Improve database performance 6 | 5,1,Implement User Authentication,2023-03-16,2023-03-29,Add secure login functionality 7 | 5,3,Write API Documentation,2023-04-01,2023-04-15,Document API endpoints 8 | 5,2,Refactor Codebase,2023-04-16,2023-04-30,Improve code structure -------------------------------------------------------------------------------- /_files/tasks_for_upload.csv: -------------------------------------------------------------------------------- 1 | status_id,complexity_id,task_name,start_date,end_date,description 2 | 1,2,Upload Code to Github,2023-01-01,2023-02-04,Uploading code on Github 3 | 2,1,Fix Bugs in Backend,2023-02-05,2023-02-15,Resolve backend issues 4 | 3,3,Design UI for Dashboard,2023-02-16,2023-03-01,Create user-friendly UI 5 | 4,2,Optimize Database Queries,2023-03-02,2023-03-15,Improve database performance 6 | 5,1,Implement User Authentication,2023-03-16,2023-03-29,Add secure login functionality 7 | 5,3,Write API Documentation,2023-04-01,2023-04-15,Document API endpoints 8 | 5,2,Refactor Codebase,2023-04-16,2023-04-30,Improve code structure 9 | 1,1,Unit Testing,2023-05-01,2023-05-10,Write and run unit tests 10 | 4,3,Integrate Payment Gateway,2023-05-11,2023-05-25,Add payment functionality 11 | 1,2,Create Admin Panel,2023-05-26,2023-06-10,Develop admin dashboard 12 | 1,1,Optimize Frontend Performance,2023-06-11,2023-06-25,Improve webpage loading speed 13 | 2,3,Implement Notifications,2023-06-26,2023-07-10,Add push notifications 14 | 3,2,Upgrade Third-Party Libraries,2023-07-11,2023-07-25,Keep dependencies up-to-date 15 | 4,1,Code Review,2023-07-26,2023-08-05,Review and improve code quality 16 | 5,3,Develop Mobile App,2023-08-06,2023-08-20,Build mobile application 17 | 5,2,Create User Guides,2023-08-21,2023-09-05,Prepare user documentation 18 | 2,1,Implement Search Functionality,2023-09-06,2023-09-20,Add search feature to the app 19 | 2,3,Perform Security Audit,2023-09-21,2023-10-05,Conduct security assessment 20 | 2,2,Setup Continuous Integration,2023-10-06,2023-10-20,Implement CI/CD pipeline 21 | 3,1,Release v1.0,2023-10-21,2023-11-01,Official product release 22 | 1,2,Optimize API Endpoints,2023-11-02,2023-11-15,Improve efficiency of API calls 23 | 2,1,Fix Cross-Browser Compatibility,2023-11-16,2023-11-30,Ensure app works on all browsers 24 | 3,3,Conduct User Feedback Session,2023-12-01,2023-12-15,Gather feedback for product improvement 25 | 3,2,Update Documentation for New Features,2023-12-16,2023-12-29,Keep documentation current 26 | 5,1,Enhance Error Handling,2023-01-01,2023-01-15,Improve error messages and handling 27 | 3,3,Localization for Multiple Languages,2023-01-16,2023-01-29,Implement language support 28 | 5,2,Performance Monitoring Setup,2023-02-01,2023-02-15,Integrate monitoring tools 29 | 1,1,Implement Two-Factor Authentication,2023-02-16,2023-02-28,Enhance security with 2FA 30 | 1,3,Upgrade Frontend Framework,2023-03-01,2023-03-15,Migrate to the latest frontend framework 31 | 1,2,Database Backup and Recovery,2023-03-16,2023-03-27,Implement robust backup system -------------------------------------------------------------------------------- /_files/wrong_file.csv: -------------------------------------------------------------------------------- 1 | status_id,complexity_id,task_name,start_date,end_date,description 2 | 1,2,Upload Code to Github,2023-01-01,2023-02-04,Uploading code on Github 3 | 2,1,Fix Bugs in Backend,2023-02-05,2023-02-15,Resolve backend issues 4 | 3,3,Design UI for Dashboard,2023-02-16,2023-03-01,Create user-friendly UI 5 | 4,2,Optimize Database Queries,2023-03-02,2023-03-15,Improve database performance 6 | 5,1,Implement User Authentication,2023-03-16,2023-03-29,Add secure login functionality 7 | 5,3,Write API Documentation,2023-04-01,2023-04-15,Document API endpoints 8 | 5,2,Refactor Codebase,2023-04-16,2023-04-30,Improve code structure 9 | 1,1,Unit Testing,2023-05-01,2023-05-10,Write and run unit tests 10 | Improve efficiency of API calls 11 | 2,1,Fix Cross-Browser Compatibility,2023-11-16,2023-11-30,Ensure app works on all browsers 12 | 3,3,Conduct User Feedback Session,2023-12-01,2023-12-15,Gather feedback for product improvement 13 | 3,2,Update Documentation for New Features,2023-12-16,2023-12-29,Keep documentation current 14 | 5,1,Enhance Error Handling,2023-01-01,2023-01-15,Improve error messages and handling 15 | 3,3,Localization for Multiple Languages,2023-01-16,2023-01-29,Implement language support 16 | 511111,21111,Performance Monitoring Setup,2023-02-01,2023-02-15,Integrate monitoring tools 17 | 1,1,Implement Two-Factor Authentication,2023-02-16,2023-02-28,Enhance security with 2FA 18 | 1,3,Upgrade Frontend Framework,2023-03-01,2023-03-15,Migrate to the latest frontend framework 19 | 1,2,Database Backup and Recovery,2023-03-16,2023-03-27,Implement robust backup system -------------------------------------------------------------------------------- /config/app_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/gofiber/template/html/v2" 6 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 7 | ) 8 | 9 | func ConfigStaticFiles(app *fiber.App) { 10 | app.Static("/", "./static") 11 | } 12 | 13 | func GetTemplateEngine() *html.Engine { 14 | engine := html.New("./templates", ".html") 15 | return engine 16 | } 17 | 18 | func ListenAddr() string { 19 | return GetEnv("APP_HOST")+":"+GetEnv("APP_PORT") 20 | } 21 | 22 | func ItemsPerPage() int { 23 | return helpers.ConvertToInt(GetEnv("APP_ITEMS_PER_PAGE")) 24 | } 25 | 26 | func UploadImagePath() string { 27 | return GetEnv("UPLOAD_IMAGE_PATH") 28 | } 29 | 30 | func UploadDocumentPath() string { 31 | return GetEnv("UPLOAD_DOCUMENT_PATH") 32 | } 33 | -------------------------------------------------------------------------------- /config/db_connection.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "gorm.io/driver/mysql" 6 | ) 7 | 8 | func ConnectDB() (*gorm.DB, error) { 9 | dsn := DatabaseURL() 10 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 11 | if err != nil { 12 | return nil, err 13 | } 14 | return db, nil 15 | } 16 | 17 | func DisconnectDB(db *gorm.DB) { 18 | conn, err := db.DB() 19 | if err != nil { 20 | panic("Failed to disconnect DB") 21 | } 22 | conn.Close() 23 | } 24 | 25 | func DatabaseURL() string { 26 | return GetEnv("DATABASE_URL") 27 | } -------------------------------------------------------------------------------- /config/env_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "github.com/joho/godotenv" 7 | ) 8 | 9 | func LoadDotEnv() { 10 | err := godotenv.Load(".env") 11 | if err != nil { 12 | log.Fatal("Error while loading .env file") 13 | } 14 | } 15 | 16 | func GetEnv(key string) string { 17 | LoadDotEnv() 18 | value, exists := os.LookupEnv(key) 19 | if !exists { 20 | log.Fatalf("Environment variable %s not found", key) 21 | } 22 | return value 23 | } 24 | -------------------------------------------------------------------------------- /config/log_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | 5 | "go.uber.org/zap" 6 | "go.uber.org/zap/zapcore" 7 | "github.com/gofiber/fiber/v2" 8 | "gopkg.in/natefinch/lumberjack.v2" 9 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 10 | ) 11 | 12 | 13 | func NewLogger(logFileName string) *zap.Logger { 14 | lumberjackLogger := &lumberjack.Logger{ 15 | Filename: LogRootPath() +"/"+logFileName, 16 | MaxSize: LogMaxFileSize(), 17 | MaxBackups: LogMaxBackups(), 18 | MaxAge: LogMaxAge(), 19 | Compress: true, 20 | } 21 | // Create a zap core that writes logs to the lumberjack logger 22 | encoderConfig := zap.NewProductionEncoderConfig() 23 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 24 | zapCore := zapcore.NewCore( 25 | zapcore.NewJSONEncoder(encoderConfig), 26 | zapcore.AddSync(lumberjackLogger), 27 | zap.DebugLevel, 28 | ) 29 | // Create a logger with the zap core 30 | logger := zap.New(zapCore) 31 | return logger 32 | } 33 | 34 | 35 | 36 | func LogRequestPath(ctx *fiber.Ctx) zap.Field { 37 | return zap.String("path", ctx.Path()) 38 | } 39 | 40 | func LogRootPath() string { 41 | return GetEnv("LOG_ROOT_PATH") 42 | } 43 | 44 | func LogMaxFileSize() int { 45 | return helpers.ConvertToInt(GetEnv("LOG_MAX_SIZE")) 46 | } 47 | 48 | func LogMaxAge() int { 49 | return helpers.ConvertToInt(GetEnv("LOG_MAX_AGE")) 50 | } 51 | 52 | func LogMaxBackups() int { 53 | return helpers.ConvertToInt(GetEnv("LOG_MAX_BACKUPS")) 54 | } 55 | 56 | 57 | -------------------------------------------------------------------------------- /config/mail_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 5 | ) 6 | 7 | func DefaultEmailService() *helpers.EmailService { 8 | return &helpers.EmailService{ 9 | SMTPHost: MailSMTPHost(), 10 | SMTPPort: MailSMTPPort(), 11 | Username: MailUser(), 12 | Password: MailPassword(), 13 | } 14 | } 15 | 16 | func MailUser() string { 17 | return GetEnv("MAIL_USER") 18 | } 19 | 20 | func MailPassword() string { 21 | return GetEnv("MAIL_PASSWORD") 22 | } 23 | 24 | func MailSMTPHost() string { 25 | return GetEnv("MAIL_SMTP_HOST") 26 | } 27 | 28 | func MailSMTPPort() int { 29 | return helpers.ConvertToInt(GetEnv("MAIL_SMTP_PORT")) 30 | } 31 | -------------------------------------------------------------------------------- /config/session_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | "github.com/gofiber/storage/mysql" 6 | "github.com/gofiber/fiber/v2/middleware/session" 7 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 8 | ) 9 | 10 | func GetSessionStore() *session.Store { 11 | storage := mysql.New(mysql.Config{ 12 | ConnectionURI: DatabaseURL(), 13 | Reset: false, 14 | GCInterval: time.Duration(sessionExpiration()) * time.Minute, 15 | }) 16 | store := session.New(session.Config{ 17 | Storage: storage, 18 | }) 19 | return store 20 | } 21 | 22 | func sessionExpiration() int { 23 | return helpers.ConvertToInt(GetEnv("APP_SESSION_EXPIRATION")) 24 | } -------------------------------------------------------------------------------- /controllers/auth_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gofiber/fiber/v2" 6 | "github.com/ortizdavid/golang-fiber-webapp/config" 7 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 8 | "github.com/ortizdavid/golang-fiber-webapp/models" 9 | ) 10 | 11 | type AuthController struct { 12 | } 13 | 14 | var loggerAuth = config.NewLogger("auth-logs.log") 15 | 16 | func (auth AuthController) RegisterRoutes(router *fiber.App) { 17 | group := router.Group("/auth") 18 | group.Get("/login", auth.loginForm) 19 | group.Post("/login", auth.login) 20 | group.Get("/logout", auth.logout) 21 | group.Get("/recover-password/:token", auth.recoverPasswordForm) 22 | group.Post("/recover-password/:token", auth.recoverPassword) 23 | group.Get("/get-recovery-link", auth.getRecoveryLinkForm) 24 | group.Post("/get-recovery-link", auth.getRecoveryLink) 25 | } 26 | 27 | 28 | func (AuthController) loginForm(c *fiber.Ctx) error { 29 | return c.Render("auth/login", fiber.Map{ 30 | "Title": "Authentication", 31 | }) 32 | } 33 | 34 | func (AuthController) login(c *fiber.Ctx) error { 35 | var userModel models.UserModel 36 | userName := c.FormValue("username") 37 | password := c.FormValue("password") 38 | user, _ := userModel.FindByUserName(userName) 39 | userExists, _ := userModel.ExistsActiveUser(userName) 40 | hashedPassword := user.Password 41 | 42 | if userExists && helpers.CheckPassword(hashedPassword, password) { 43 | store := config.GetSessionStore() 44 | session, _ := store.Get(c) 45 | session.Set("username", userName) 46 | session.Set("password", hashedPassword) 47 | session.Save() 48 | user.Token = helpers.GenerateRandomToken() 49 | userModel.Update(user) 50 | loggerAuth.Info(fmt.Sprintf("User '%s' authenticated sucessfully!", userName)) 51 | return c.Redirect("/home") 52 | } else { 53 | loggerAuth.Error(fmt.Sprintf("User '%s' failed to login", userName)) 54 | return c.Status(401).Redirect("/auth/login") 55 | } 56 | } 57 | 58 | 59 | func (AuthController) logout(c *fiber.Ctx) error { 60 | loggedUser := GetLoggedUser(c) 61 | store := config.GetSessionStore() 62 | session, err := store.Get(c) 63 | if err != nil { 64 | return c.Status(500).SendString(err.Error()) 65 | } 66 | err = session.Destroy() 67 | if err != nil { 68 | return c.Status(500).SendString(err.Error()) 69 | } 70 | loggerAuth.Info(fmt.Sprintf("User '%s' logged out", loggedUser.UserName)) 71 | return c.Redirect("/auth/login") 72 | } 73 | 74 | 75 | func (AuthController) getRecoveryLinkForm(c *fiber.Ctx) error { 76 | return c.Render("auth/get-recovery-link", fiber.Map{ 77 | "Title": "Recovery Link", 78 | }) 79 | } 80 | 81 | 82 | func (AuthController) getRecoveryLink(c *fiber.Ctx) error { 83 | var userModel models.UserModel 84 | email := c.FormValue("email") 85 | user, err := userModel.FindByUserName(email) 86 | 87 | if err != nil { 88 | return c.Status(404).SendString(err.Error()) 89 | } 90 | 91 | emailService := config.DefaultEmailService() 92 | recoverLink := fmt.Sprintf("http://%s/auth/recover-password/%s", config.ListenAddr(), user.Token) 93 | htmlBody := ` 94 | 95 | 96 |

Password recovery!

97 |

Hi, `+user.UserName+`!

98 |

click on the link below
`+recoverLink+`

99 | 100 | ` 101 | err = emailService.SendHTMLEmail(email, "Password Recovery", htmlBody) 102 | if err != nil { 103 | fmt.Println("Error sending HTML email:", err) 104 | } 105 | loggerAuth.Info(fmt.Sprintf("User '%s' recovered password", email), config.LogRequestPath(c)) 106 | return c.Redirect("/auth/get-recovery-link") 107 | } 108 | 109 | 110 | func (AuthController) recoverPasswordForm(c *fiber.Ctx) error { 111 | token := c.Params("token") 112 | user, err := models.UserModel{}.FindByToken(token) 113 | 114 | if err != nil { 115 | return c.Status(404).SendString(err.Error()) 116 | } 117 | return c.Render("auth/recover-password", fiber.Map{ 118 | "Title": "Password Recovery", 119 | "User": user, 120 | }) 121 | } 122 | 123 | 124 | func (AuthController) recoverPassword(c *fiber.Ctx) error { 125 | var userModel models.UserModel 126 | password := c.FormValue("password") 127 | token := c.Params("token") 128 | 129 | user, err := userModel.FindByToken(token) 130 | if err != nil { 131 | return c.Status(404).SendString(err.Error()) 132 | } 133 | 134 | user.Password = helpers.HashPassword(password) 135 | user.Token = helpers.GenerateRandomToken() 136 | userModel.Update(user) 137 | 138 | //enviar os credenciais por email 139 | emailService := config.DefaultEmailService() 140 | htmlBody := ` 141 | 142 | 143 |

Password changed successfully!

144 |

Hi, `+user.UserName+`!

145 |

New password is: `+password+`

146 | 147 | ` 148 | err = emailService.SendHTMLEmail(user.UserName, "New Password", htmlBody) 149 | if err != nil { 150 | fmt.Println("Error sending HTML email:", err) 151 | } 152 | 153 | loggerAuth.Info(fmt.Sprintf("User '%s' recovered password", user.UserName), config.LogRequestPath(c)) 154 | return c.Redirect("/auth/login") 155 | } 156 | -------------------------------------------------------------------------------- /controllers/back_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/ortizdavid/golang-fiber-webapp/models" 6 | ) 7 | 8 | type BackController struct { 9 | } 10 | 11 | func (back BackController) RegisterRoutes(router *fiber.App) { 12 | router.Get("/home", back.home) 13 | router.Get("/user-data", back.userData) 14 | router.Get("/change-password", back.changePassword) 15 | router.Get("/upload-image", back.uploadImage) 16 | } 17 | 18 | func (BackController) home(c *fiber.Ctx) error { 19 | loggedUser := GetLoggedUser(c) 20 | statistics := models.GetStatisticsCount() 21 | if IsUserNomal(c) { 22 | statistics = models.GetStatisticsCountByUser(loggedUser.UserId) 23 | } 24 | return c.Render("back-office/home", fiber.Map{ 25 | "Title": "Home", 26 | "Statistics": statistics, 27 | "LoggedUser": loggedUser, 28 | }) 29 | } 30 | 31 | func (BackController) userData(c *fiber.Ctx) error { 32 | return c.Render("back-office/user-data", fiber.Map{ 33 | "Title": "User Data", 34 | "LoggedUser": GetLoggedUser(c), 35 | }) 36 | } 37 | 38 | func (BackController) changePassword(c *fiber.Ctx) error { 39 | return c.Render("back-office/change-password", fiber.Map{ 40 | "Title": "Change Password", 41 | "LoggedUser": GetLoggedUser(c), 42 | }) 43 | } 44 | 45 | func (BackController) uploadImage(c *fiber.Ctx) error { 46 | return c.Render("back-office/upload-image", fiber.Map{ 47 | "Title": "Upload Image", 48 | "LoggedUser": GetLoggedUser(c), 49 | }) 50 | } -------------------------------------------------------------------------------- /controllers/front_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import "github.com/gofiber/fiber/v2" 4 | 5 | type FrontController struct { 6 | } 7 | 8 | func (FrontController) index(ctx *fiber.Ctx) error { 9 | return ctx.Redirect("/auth/login") 10 | } 11 | 12 | func (front FrontController) RegisterRoutes(router *fiber.App) { 13 | router.Get("/", front.index) 14 | } -------------------------------------------------------------------------------- /controllers/helper_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gofiber/fiber/v2" 6 | "github.com/ortizdavid/golang-fiber-webapp/config" 7 | "github.com/ortizdavid/golang-fiber-webapp/entities" 8 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 9 | "github.com/ortizdavid/golang-fiber-webapp/models" 10 | ) 11 | 12 | func GetLoggedUser(c *fiber.Ctx) entities.UserData { 13 | store := config.GetSessionStore() 14 | session, _ := store.Get(c) 15 | userName := helpers.ConvertToString(session.Get("username")) 16 | password := helpers.ConvertToString(session.Get("password")) 17 | loggedUser, _ := models.UserModel{}.GetByUserNameAndPassword(userName, password) 18 | return loggedUser 19 | } 20 | 21 | func IsUserNomal(c *fiber.Ctx) bool { 22 | loggedUser := GetLoggedUser(c) 23 | return loggedUser.RoleCode == "normal" 24 | } 25 | 26 | func IsUserAdmin(c *fiber.Ctx) bool { 27 | loggedUser := GetLoggedUser(c) 28 | return loggedUser.RoleCode == "admin" 29 | } 30 | 31 | func PaginationHandler(c *fiber.Ctx) error { 32 | var pagination helpers.Pagination 33 | pageNumber := pagination.GetPageNumber(c, "page") 34 | return c.Redirect(fmt.Sprintf("/?page=%d", pageNumber)) 35 | } 36 | 37 | -------------------------------------------------------------------------------- /controllers/interface_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import "github.com/gofiber/fiber/v2" 4 | 5 | type InterfaceController interface { 6 | index(c *fiber.Ctx) error 7 | details(c *fiber.Ctx) error 8 | addForm(c *fiber.Ctx) error 9 | add(c *fiber.Ctx) error 10 | editForm(c *fiber.Ctx) error 11 | edit(c *fiber.Ctx) error 12 | searchForm(c *fiber.Ctx) error 13 | search(c *fiber.Ctx) error 14 | RegisterRoutes(router *fiber.App) 15 | } -------------------------------------------------------------------------------- /controllers/middleware.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "github.com/gofiber/fiber/v2" 7 | "github.com/ortizdavid/golang-fiber-webapp/config" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | 12 | var requestLogger = config.NewLogger("requests.log") 13 | 14 | func RequestLoggerMiddleware(c *fiber.Ctx) error { 15 | requestLogger.Info("Request", 16 | zap.String("Method", c.Method()), 17 | zap.String("Path", c.Path()), 18 | zap.String("StatusCode", fmt.Sprintf("%d", c.Response().StatusCode())), 19 | ) 20 | return c.Next() 21 | } 22 | 23 | func AuthenticationMiddleware(c *fiber.Ctx) error { 24 | requestedPath := c.Path() 25 | if requestedPath == "/" || 26 | strings.HasPrefix(requestedPath, "/image") || 27 | strings.HasPrefix(requestedPath, "/css") || 28 | strings.HasPrefix(requestedPath, "/js") || 29 | strings.HasPrefix(requestedPath, "/auth") { 30 | return c.Next() 31 | } 32 | if !IsUserAuthenticated(c) { 33 | loggerAuth.Error(fmt.Sprintf("Authentication failed at: %s", requestedPath)) 34 | return c.Status(500).Render("error/authentication", fiber.Map{ 35 | "Title": "Authentication Error", 36 | }) 37 | } 38 | return c.Next() 39 | } 40 | 41 | 42 | func IsUserAuthenticated(c *fiber.Ctx) bool { 43 | loggedUser := GetLoggedUser(c) 44 | if loggedUser.UserId == 0 && loggedUser.RoleId == 0 { 45 | return false 46 | } 47 | return true 48 | } 49 | -------------------------------------------------------------------------------- /controllers/report_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gofiber/fiber/v2" 6 | "github.com/ortizdavid/golang-fiber-webapp/config" 7 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 8 | "github.com/ortizdavid/golang-fiber-webapp/models" 9 | ) 10 | 11 | type ReportController struct { 12 | } 13 | 14 | var loggerReport = config.NewLogger("report-logs.log") 15 | 16 | func (report ReportController) RegisterRoutes(router *fiber.App) { 17 | router.Get("/reports", report.reportHandler) 18 | router.Get("/reports/statistics", report.statisticsReportHandler) 19 | } 20 | 21 | func (ReportController) reportHandler(c *fiber.Ctx) error { 22 | param := c.Query("param") 23 | loggedUser := GetLoggedUser(c) 24 | var report models.ReportModel 25 | var tableReport models.TableReport 26 | var pdfGen helpers.HtmlPdfGenerator 27 | 28 | switch param { 29 | case "users": 30 | tableReport = report.GetAllUsers() 31 | case "tasks": 32 | tableReport = report.GetAllTasks() 33 | case "completed-tasks": 34 | tableReport = report.GetAllCompletedTasks() 35 | case "pending-tasks": 36 | tableReport = report.GetAllPendingTasks() 37 | case "in-progress-tasks": 38 | tableReport = report.GetAllInProgressTasks() 39 | case "cancelled-tasks": 40 | tableReport = report.GetAllCancelledTasks() 41 | case "blocked-tasks": 42 | tableReport = report.GetAllBlockedTasks() 43 | case "": 44 | return c.Render("reports/index", fiber.Map{ 45 | "Title": "Reports", 46 | "LoggedUser": GetLoggedUser(c), 47 | }) 48 | } 49 | //----------------------- 50 | templateFile := param +".html" 51 | title := "Report: " +tableReport.Title 52 | fileName := title +".pdf" 53 | data := map[string]any{ 54 | "Title": title, 55 | "AppName": "Task Management App", 56 | "Rows": tableReport.Rows, 57 | "Count": tableReport.Count, 58 | } 59 | //----------- Render PDF 60 | pdfBytes, err := pdfGen.GeneratePDF(fmt.Sprintf("templates/reports/%s", templateFile), data) 61 | if err != nil { 62 | return c.Status(500).SendString(err.Error()) 63 | } 64 | pdfGen.SetOutput(c, pdfBytes, fileName) 65 | loggerReport.Info(fmt.Sprintf("User '%s' generated '%s' Report", loggedUser.UserName, tableReport.Title)) 66 | return nil 67 | } 68 | 69 | func (ReportController) statisticsReportHandler(c *fiber.Ctx) error { 70 | loggedUser := GetLoggedUser(c) 71 | var pdfGen helpers.HtmlPdfGenerator 72 | templateFile := "statistics.html" 73 | fileName := "Statistic Report.pdf" 74 | data := map[string]any{ 75 | "Title": "Statistic Report", 76 | "AppName": "Task Management App", 77 | "Statistics": models.GetStatisticsCount(), 78 | "LoggedUser": GetLoggedUser(c), 79 | } 80 | //----------------------- 81 | pdfBytes, err := pdfGen.GeneratePDF(fmt.Sprintf("templates/reports/%s", templateFile), data) 82 | if err != nil { 83 | return c.Status(500).SendString(err.Error()) 84 | } 85 | pdfGen.SetOutput(c, pdfBytes, fileName) 86 | loggerReport.Info(fmt.Sprintf("User '%s' generated '%s' Report", loggedUser.UserName, "Statistics")) 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /controllers/setup.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import "github.com/gofiber/fiber/v2" 4 | 5 | func SetupRoutes(router *fiber.App) { 6 | FrontController{}.RegisterRoutes(router) 7 | AuthController{}.RegisterRoutes(router) 8 | BackController{}.RegisterRoutes(router) 9 | TaskController{}.RegisterRoutes(router) 10 | UserController{}.RegisterRoutes(router) 11 | ReportController{}.RegisterRoutes(router) 12 | StatisticController{}.RegisterRoutes(router) 13 | } -------------------------------------------------------------------------------- /controllers/statistic_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/ortizdavid/golang-fiber-webapp/models" 6 | ) 7 | 8 | type StatisticController struct { 9 | } 10 | 11 | func (statistic StatisticController) RegisterRoutes(router *fiber.App) { 12 | router.Get("/statistics", statistic.index) 13 | } 14 | 15 | func (StatisticController) index(c *fiber.Ctx) error { 16 | loggedUser := GetLoggedUser(c) 17 | statistics := models.GetStatisticsCount() 18 | if loggedUser.RoleCode == "normal" { 19 | statistics = models.GetStatisticsCountByUser(loggedUser.UserId) 20 | } 21 | return c.Render("statistic/index", fiber.Map{ 22 | "Title": "Statistics", 23 | "LoggedUser": loggedUser, 24 | "Statistics": statistics, 25 | }) 26 | } -------------------------------------------------------------------------------- /controllers/task_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/csv" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/gofiber/fiber/v2" 9 | "github.com/ortizdavid/golang-fiber-webapp/config" 10 | "github.com/ortizdavid/golang-fiber-webapp/entities" 11 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 12 | "github.com/ortizdavid/golang-fiber-webapp/models" 13 | ) 14 | 15 | type TaskController struct { 16 | } 17 | 18 | var loggerTask = config.NewLogger("task-logs.log") 19 | 20 | func (task TaskController) RegisterRoutes(router *fiber.App) { 21 | group := router.Group("/tasks") 22 | group.Get("/", task.index) 23 | group.Get("/page/:pageNumber", PaginationHandler) 24 | group.Get("/add", task.addForm) 25 | group.Post("/add", task.add) 26 | group.Get("/:id/details", task.details) 27 | group.Get("/:id/edit", task.editForm) 28 | group.Post("/:id/edit", task.edit) 29 | group.Get("/search", task.searchForm) 30 | group.Post("/search", task.search) 31 | group.Get("/:id/add-attachment", task.addAttachmentForm) 32 | group.Post("/:id/add-attachment", task.addAttachment) 33 | group.Get("/:id/view-attachment", task.viewAttachment) 34 | group.Get("/upload-csv", task.uploadCSVForm) 35 | group.Post("/upload-csv", task.uploadCSV) 36 | } 37 | 38 | 39 | func (TaskController) index(c *fiber.Ctx) error { 40 | 41 | var pagination helpers.Pagination 42 | var taskModel models.TaskModel 43 | var tasks []entities.TaskData 44 | var count int 45 | 46 | loggedUser := GetLoggedUser(c) 47 | pageNumber := pagination.GetPageNumber(c, "page") 48 | itemsPerPage := config.ItemsPerPage() 49 | startIndex := pagination.CalculateStartIndex(pageNumber, itemsPerPage) 50 | tasks, _ = taskModel.FindAllDataLimit(startIndex, itemsPerPage) 51 | countTasks, _ := taskModel.Count() 52 | count = int(countTasks) 53 | totalPages := pagination.CalculateTotalPages(count, itemsPerPage) 54 | 55 | if count > 0 && pageNumber > totalPages { 56 | return c.Status(500).Render("error/pagination", fiber.Map{ 57 | "Title": "Tasks", 58 | "TotalPages": totalPages, 59 | "LoggedUser": loggedUser, 60 | }) 61 | } 62 | if loggedUser.RoleCode != "normal" { 63 | return c.Render("task/index", fiber.Map{ 64 | "Title": "Tasks", 65 | "Tasks": tasks, 66 | "Pagination": helpers.NewPaginationRender(pageNumber), 67 | "Count": count, 68 | "LoggedUser": loggedUser, 69 | }) 70 | } 71 | userTasks, _ := taskModel.FindAllDataByUserIdLimit(loggedUser.UserId, startIndex, itemsPerPage) 72 | countByUser, _ := taskModel.CountByUser(loggedUser.UserId) 73 | count = int(countByUser) 74 | return c.Render("task/my-tasks", fiber.Map{ 75 | "Title": "My Tasks", 76 | "Tasks": userTasks, 77 | "Pagination": helpers.NewPaginationRender(pageNumber), 78 | "Count": count, 79 | "LoggedUser": loggedUser, 80 | }) 81 | } 82 | 83 | 84 | func (TaskController) details(c *fiber.Ctx) error { 85 | id := c.Params("id") 86 | task, _ := models.TaskModel{}.GetDataByUniqueId(id) 87 | return c.Render("task/details", fiber.Map{ 88 | "Title": "Task Details", 89 | "Task": task, 90 | "LoggedUser": GetLoggedUser(c), 91 | }) 92 | } 93 | 94 | 95 | func (TaskController) addForm(c *fiber.Ctx) error { 96 | complexities, _ := models.TaskComplexityModel{}.FindAll() 97 | statuses, _ := models.TaskStatusModel{}.FindAll() 98 | return c.Render("task/add", fiber.Map{ 99 | "Title": "Add Tasks", 100 | "Complexities": complexities, 101 | "Statuses": statuses, 102 | "LoggedUser": GetLoggedUser(c), 103 | }) 104 | } 105 | 106 | 107 | func (TaskController) add(c *fiber.Ctx) error { 108 | loggedUser := GetLoggedUser(c) 109 | taskName := c.FormValue("task_name") 110 | statusId := c.FormValue("status_id") 111 | complexityId := c.FormValue("complexity_id") 112 | description := c.FormValue("description") 113 | startDate := c.FormValue("start_date") 114 | endDate := c.FormValue("end_date") 115 | 116 | var taskModel models.TaskModel 117 | task := entities.Task{ 118 | TaskId: 0, 119 | UserId: loggedUser.UserId, 120 | StatusId: helpers.ConvertToInt(statusId), 121 | ComplexityId: helpers.ConvertToInt(complexityId), 122 | TaskName: taskName, 123 | StartDate: startDate, 124 | EndDate: endDate, 125 | Description: description, 126 | Attachment: "", 127 | UniqueId: helpers.GenerateUUID(), 128 | CreatedAt: time.Now(), 129 | UpdatedAt: time.Now(), 130 | } 131 | _, err := taskModel.Create(task) 132 | if err != nil { 133 | return c.Status(500).SendString(err.Error()) 134 | } 135 | loggerTask.Info(fmt.Sprintf("User '%s' added Task '%s'", loggedUser.UserName, taskName)) 136 | return c.Redirect("/tasks") 137 | } 138 | 139 | 140 | func (TaskController) editForm(c *fiber.Ctx) error { 141 | loggedUser := GetLoggedUser(c) 142 | id := c.Params("id") 143 | task, _ := models.TaskModel{}.GetDataByUniqueId(id) 144 | statuses, _ := models.TaskStatusModel{}.FindAll() 145 | complexities, _ := models.TaskComplexityModel{}.FindAll() 146 | return c.Render("task/edit", fiber.Map{ 147 | "Title": "Edit Task", 148 | "Task": task, 149 | "Statuses": statuses, 150 | "Complexities": complexities, 151 | "LoggedUser": loggedUser, 152 | }) 153 | } 154 | 155 | 156 | func (TaskController) edit(c *fiber.Ctx) error { 157 | id := c.Params("id") 158 | taskName := c.FormValue("task_name") 159 | statusId := c.FormValue("status_id") 160 | complexityId := c.FormValue("complexity_id") 161 | description := c.FormValue("description") 162 | startDate := c.FormValue("start_date") 163 | endDate := c.FormValue("end_date") 164 | 165 | var taskModel models.TaskModel 166 | task, _ := taskModel.FindByUniqueId(id) 167 | task.TaskName = taskName 168 | task.StatusId = helpers.ConvertToInt(statusId) 169 | task.ComplexityId = helpers.ConvertToInt(complexityId) 170 | task.Description = description 171 | task.StartDate = startDate 172 | task.EndDate = endDate 173 | task.UpdatedAt = time.Now() 174 | _, err := taskModel.Update(task) 175 | if err != nil { 176 | return c.Status(500).SendString(err.Error()) 177 | } 178 | loggerTask.Info(fmt.Sprintf("Task '%s' Added ", taskName)) 179 | return c.Redirect(fmt.Sprintf("/tasks/%s/details", id)) 180 | } 181 | 182 | 183 | func (TaskController) searchForm(c *fiber.Ctx) error { 184 | return c.Render("task/search", fiber.Map{ 185 | "Title": "Search Tasks", 186 | "LoggedUser": GetLoggedUser(c), 187 | }) 188 | } 189 | 190 | 191 | func (TaskController) search(c *fiber.Ctx) error { 192 | param := c.FormValue("search_param") 193 | results, err := models.TaskModel{}.Search(param) 194 | if err != nil { 195 | return c.Status(404).SendString(err.Error()) 196 | } 197 | count := len(results) 198 | loggedUser := GetLoggedUser(c) 199 | loggerTask.Info(fmt.Sprintf("User '%s' Searched for Task '%v' and Found %d results", loggedUser.UserName, param, count)) 200 | return c.Render("task/search-results", fiber.Map{ 201 | "Title": "Results", 202 | "Results": results, 203 | "Param": param, 204 | "Count": count, 205 | "LoggedUser": loggedUser, 206 | }) 207 | } 208 | 209 | 210 | func (TaskController) addAttachmentForm(c *fiber.Ctx) error { 211 | id := c.Params("id") 212 | task, _ := models.TaskModel{}.FindByUniqueId(id) 213 | return c.Render("task/add-attachment", fiber.Map{ 214 | "Title": "Add Attachment", 215 | "Task": task, 216 | "LoggedUser": GetLoggedUser(c), 217 | }) 218 | } 219 | 220 | 221 | func (TaskController) addAttachment(c *fiber.Ctx) error { 222 | id := c.Params("id") 223 | attachment, _ := helpers.UploadFile(c, "attachment", "document", config.UploadDocumentPath()) 224 | loggedUser := GetLoggedUser(c) 225 | 226 | var taskModel models.TaskModel 227 | task, err := taskModel.FindByUniqueId(id) 228 | if err != nil { 229 | return c.Status(404).SendString(err.Error()) 230 | } 231 | task.Attachment = attachment 232 | task.UpdatedAt = time.Now() 233 | taskModel.Update(task) 234 | loggerTask.Info(fmt.Sprintf("User '%s' added attachment for task '%s' ", loggedUser.UserName, task.TaskName)) 235 | return c.Redirect("/tasks/"+id+"/details") 236 | } 237 | 238 | 239 | func (TaskController) viewAttachment(c *fiber.Ctx) error { 240 | id := c.Params("id") 241 | task, err := models.TaskModel{}.FindByUniqueId(id) 242 | if err != nil { 243 | return c.Status(500).SendString(err.Error()) 244 | } 245 | return c.SendFile("./static/uploads/docs/"+task.Attachment) 246 | } 247 | 248 | 249 | func (TaskController) uploadCSVForm(c *fiber.Ctx) error { 250 | return c.Render("task/upload-csv", fiber.Map{ 251 | "Title": "Upload Tasks from CSV file", 252 | "LoggedUser": GetLoggedUser(c), 253 | }) 254 | } 255 | 256 | 257 | func (TaskController) uploadCSV(c *fiber.Ctx) error { 258 | var taskModel models.TaskModel 259 | loggedUser := GetLoggedUser(c) 260 | 261 | file, err := c.FormFile("csv_file") 262 | if err != nil { 263 | return c.Status(500).SendString(err.Error()) 264 | } 265 | 266 | src, err := file.Open() 267 | if err != nil { 268 | return c.Status(500).SendString(err.Error()) 269 | } 270 | defer src.Close() 271 | 272 | reader := csv.NewReader(src) 273 | if err := models.SkipCSVHeader(reader); err != nil { 274 | return c.Status(500).SendString(err.Error()) 275 | } 276 | 277 | tasksFromCSV, err := models.ParseTaskFromCSV(reader, loggedUser.UserId) // Parsing CSV 278 | if err != nil { 279 | return c.Status(500).SendString(err.Error()) 280 | } 281 | 282 | _, err = taskModel.CreateBatch(tasksFromCSV) // Inserting Batch 283 | if err != nil { 284 | return c.Status(500).SendString(err.Error()) 285 | } 286 | 287 | loggerTask.Info(fmt.Sprintf("User '%s' Uploaded Task from CSV File '%s'", loggedUser.UserName, file.Filename)) 288 | return c.Redirect("/tasks") 289 | } -------------------------------------------------------------------------------- /controllers/user_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "github.com/gofiber/fiber/v2" 7 | "github.com/ortizdavid/golang-fiber-webapp/config" 8 | "github.com/ortizdavid/golang-fiber-webapp/entities" 9 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 10 | "github.com/ortizdavid/golang-fiber-webapp/models" 11 | ) 12 | 13 | type UserController struct { 14 | } 15 | 16 | type UserValidation struct { 17 | Username string `form:"username" validate:"required,min=3"` 18 | Password string `form:"password" validate:"required"` 19 | RoleId string `form:"role_id" validate:"required"` 20 | } 21 | 22 | var loggerUser = config.NewLogger("user-logs.log") 23 | 24 | func (user UserController) RegisterRoutes(router *fiber.App) { 25 | group := router.Group("/users") 26 | group.Get("/", user.index) 27 | group.Get("/page/:pageNumber", PaginationHandler) 28 | group.Get("/add", user.addForm) 29 | group.Post("/add", user.add) 30 | group.Get("/:id/details", user.details) 31 | group.Get("/:id/edit", user.editForm) 32 | group.Post("/:id/edit", user.edit) 33 | group.Get("/:id/deactivate", user.deactivateForm) 34 | group.Post("/:id/deactivate", user.deactivate) 35 | group.Get("/:id/activate", user.activateForm) 36 | group.Post("/:id/activate", user.activate) 37 | group.Get("/add-image", user.addImageForm) 38 | group.Post("/add-image", user.addImage) 39 | group.Get("/search", user.searchForm) 40 | group.Post("/search", user.search) 41 | } 42 | 43 | func (UserController) index(c *fiber.Ctx) error { 44 | var pagination helpers.Pagination 45 | var userModel models.UserModel 46 | 47 | loggedUser := GetLoggedUser(c) 48 | pageNumber := pagination.GetPageNumber(c, "page") 49 | itemsPerPage := config.ItemsPerPage() 50 | startIndex := pagination.CalculateStartIndex(pageNumber, itemsPerPage) 51 | users, _ := userModel.FindAllDataLimit(startIndex, itemsPerPage) 52 | countUsers, _ := userModel.Count() 53 | count := int(countUsers) 54 | totalPages := pagination.CalculateTotalPages(count, itemsPerPage) 55 | 56 | if count > 0 && pageNumber > totalPages { 57 | return c.Status(500).Render("error/pagination", fiber.Map{ 58 | "Title": "Tasks", 59 | "TotalPages": totalPages, 60 | "LoggedUser": loggedUser, 61 | }) 62 | } 63 | return c.Render("user/index", fiber.Map{ 64 | "Title": "Users", 65 | "Users": users, 66 | "Pagination": helpers.NewPaginationRender(pageNumber), 67 | "Count": count, 68 | "PageNumber": pageNumber, 69 | "TotalPages": totalPages, 70 | "LoggedUser": loggedUser, 71 | }) 72 | } 73 | 74 | func (UserController) details(c *fiber.Ctx) error { 75 | id := c.Params("id") 76 | user, err := models.UserModel{}.GetDataByUniqueId(id) 77 | if err != nil { 78 | return c.Status(404).SendString(err.Error()) 79 | } 80 | return c.Render("user/details", fiber.Map{ 81 | "Title": "User Details", 82 | "User": user, 83 | "LoggedUser": GetLoggedUser(c), 84 | }) 85 | } 86 | 87 | func (UserController) addForm(c *fiber.Ctx) error { 88 | roles, err := models.RoleModel{}.FindAll() 89 | if err != nil { 90 | return c.Status(404).SendString(err.Error()) 91 | } 92 | return c.Render("user/add", fiber.Map{ 93 | "Title": "Add User", 94 | "Roles": roles, 95 | "LoggedUser": GetLoggedUser(c), 96 | }) 97 | } 98 | 99 | func (UserController) add(c *fiber.Ctx) error { 100 | userName := c.FormValue("username") 101 | password := c.FormValue("password") 102 | roleId := c.FormValue("role_id") 103 | 104 | var userModel models.UserModel 105 | user := entities.User{ 106 | UserId: 0, 107 | RoleId: helpers.ConvertToInt(roleId), 108 | UserName: userName, 109 | Password: helpers.HashPassword(password), 110 | Active: "Yes", 111 | Image: "", 112 | UniqueId: helpers.GenerateUUID(), 113 | Token: helpers.GenerateRandomToken(), 114 | CreatedAt: time.Now(), 115 | UpdatedAt: time.Now(), 116 | } 117 | _, err := userModel.Create(user) 118 | if err != nil { 119 | return c.Status(500).SendString(err.Error()) 120 | } 121 | loggerUser.Info(fmt.Sprintf("User '%s' added successfully", userName)) 122 | return c.Redirect("/users") 123 | } 124 | 125 | func (UserController) editForm(c *fiber.Ctx) error { 126 | id := c.Params("id") 127 | user, _ := models.UserModel{}.GetDataByUniqueId(id) 128 | roles, _ := models.RoleModel{}.FindAll() 129 | return c.Render("user/edit", fiber.Map{ 130 | "Title": "Edit User", 131 | "Roles": roles, 132 | "User": user, 133 | "LoggedUser": GetLoggedUser(c), 134 | }) 135 | } 136 | 137 | func (UserController) edit(c *fiber.Ctx) error { 138 | id := c.Params("id") 139 | roleId := c.FormValue("role_id") 140 | var userModel models.UserModel 141 | user, _ := userModel.FindByUniqueId(id) 142 | user.RoleId = helpers.ConvertToInt(roleId) 143 | user.UpdatedAt = time.Now() 144 | user.Token = helpers.GenerateRandomToken() 145 | _, err := userModel.Update(user) 146 | if err != nil { 147 | return c.Status(500).SendString(err.Error()) 148 | } 149 | loggerUser.Info(fmt.Sprintf("User '%s' Updated successfully", user.UserName)) 150 | return c.Redirect("/users") 151 | } 152 | 153 | 154 | func (UserController) searchForm(c *fiber.Ctx) error { 155 | return c.Render("user/search", fiber.Map{ 156 | "Title": "Search Users", 157 | "LoggedUser": GetLoggedUser(c), 158 | }) 159 | } 160 | 161 | func (UserController) search(c *fiber.Ctx) error { 162 | param := c.FormValue("search_param") 163 | results, err := models.UserModel{}.Search(param) 164 | if err != nil { 165 | return c.Status(500).SendString(err.Error()) 166 | } 167 | count := len(results) 168 | loggedUser := GetLoggedUser(c) 169 | loggerUser.Info(fmt.Sprintf("User '%s' Searched for User '%v' and Found %d results", loggedUser.UserName, param, count)) 170 | return c.Render("user/search-results", fiber.Map{ 171 | "Title": "Results", 172 | "Results": results, 173 | "Param": param, 174 | "Count": count, 175 | "LoggedUser": loggedUser, 176 | }) 177 | } 178 | 179 | func (UserController) deactivateForm(c *fiber.Ctx) error { 180 | id := c.Params("id") 181 | user, err := models.UserModel{}.GetDataByUniqueId(id) 182 | if err != nil { 183 | return c.Status(404).SendString(err.Error()) 184 | } 185 | return c.Render("user/deactivate", fiber.Map{ 186 | "Title": "Deactivate User", 187 | "User": user, 188 | "LoggedUser": GetLoggedUser(c), 189 | }) 190 | } 191 | 192 | func (UserController) deactivate(c *fiber.Ctx) error { 193 | id := c.Params("id") 194 | var userModel models.UserModel 195 | user, err := userModel.FindByUniqueId(id) 196 | if err != nil { 197 | return c.Status(404).SendString(err.Error()) 198 | } 199 | user.Active = "No" 200 | user.UpdatedAt = time.Now() 201 | userModel.Update(user) 202 | loggerUser.Info(fmt.Sprintf("User '%s' deactivated successfuly", user.UserName)) 203 | return c.Redirect("/users/"+id+"/details") 204 | } 205 | 206 | func (UserController) activateForm(c *fiber.Ctx) error { 207 | id := c.Params("id") 208 | user, _ := models.UserModel{}.GetDataByUniqueId(id) 209 | return c.Render("user/activate", fiber.Map{ 210 | "Title": "Activate User", 211 | "User": user, 212 | "LoggedUser": GetLoggedUser(c), 213 | }) 214 | } 215 | 216 | func (UserController) activate(c *fiber.Ctx) error { 217 | id := c.Params("id") 218 | var userModel models.UserModel 219 | user, err := userModel.FindByUniqueId(id) 220 | if err != nil { 221 | return c.Status(404).SendString(err.Error()) 222 | } 223 | user.Active = "Yes" 224 | user.UpdatedAt = time.Now() 225 | user.Token = helpers.GenerateRandomToken() 226 | userModel.Update(user) 227 | loggerUser.Info(fmt.Sprintf("User '%s' activated sucessfully", user.UserName)) 228 | return c.Redirect("/users/"+id+"/details") 229 | } 230 | 231 | func (UserController) addImageForm(c *fiber.Ctx) error { 232 | return c.Render("user/add-image", fiber.Map{ 233 | "Title": "Add Image", 234 | "LoggedUser": GetLoggedUser(c), 235 | }) 236 | } 237 | 238 | func (UserController) addImage(c *fiber.Ctx) error { 239 | userImage, _ := helpers.UploadFile(c, "user_image", "image", config.UploadImagePath()) 240 | loggedUser := GetLoggedUser(c) 241 | var userModel models.UserModel 242 | user, err := userModel.FindById(loggedUser.UserId) 243 | if err != nil { 244 | return c.Status(404).SendString(err.Error()) 245 | } 246 | user.Image = userImage 247 | user.UpdatedAt = time.Now() 248 | userModel.Update(user) 249 | loggerUser.Info(fmt.Sprintf("User '%s' added image", user.UserName)) 250 | return c.Redirect("/user-data") 251 | } 252 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | 4 | services: 5 | 6 | webapp: 7 | build: 8 | context: . 9 | dockerfile: Dockerfile 10 | ports: 11 | - "5000:5000" 12 | depends_on: 13 | - database 14 | networks: 15 | - mynet 16 | environment: 17 | - DATABASE_URL=root:@tcp(mysql:3306)/golang_fiber_mvc 18 | 19 | database: 20 | image: mysql:latest 21 | environment: 22 | - MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} 23 | - MYSQL_DATABASE: ${DB_NAME} 24 | - MYSQL_USER: ${DB_USER} 25 | - MYSQL_PASSWORD: ${DB_PASSWORD} 26 | ports: 27 | - "3306:3306" 28 | networks: 29 | - mynet 30 | 31 | networks: 32 | mynet: 33 | driver: bridge 34 | 35 | -------------------------------------------------------------------------------- /docs/Add-Attahment.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Add-Attahment.jpg -------------------------------------------------------------------------------- /docs/Add-Task.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Add-Task.jpg -------------------------------------------------------------------------------- /docs/Add-User.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Add-User.jpg -------------------------------------------------------------------------------- /docs/Architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Architecture.jpg -------------------------------------------------------------------------------- /docs/Design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Design.jpg -------------------------------------------------------------------------------- /docs/Edit-Task.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Edit-Task.jpg -------------------------------------------------------------------------------- /docs/Home-Admin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Home-Admin.jpg -------------------------------------------------------------------------------- /docs/Reports.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Reports.jpg -------------------------------------------------------------------------------- /docs/Search-Tasks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Search-Tasks.jpg -------------------------------------------------------------------------------- /docs/Task-Details.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Task-Details.jpg -------------------------------------------------------------------------------- /docs/Task-Report.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Task-Report.jpg -------------------------------------------------------------------------------- /docs/Tasks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Tasks.jpg -------------------------------------------------------------------------------- /docs/User-Statistics.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/User-Statistics.jpg -------------------------------------------------------------------------------- /docs/Users.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/docs/Users.jpg -------------------------------------------------------------------------------- /entities/role.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Role struct { 4 | RoleId int `gorm:"primaryKey;autoIncrement"` 5 | RoleName string `gorm:"column:role_name;type:varchar(100)"` 6 | Code string `gorm:"column:code;type:varchar(20)"` 7 | } 8 | 9 | func (Role) TableName() string { 10 | return "roles" 11 | } -------------------------------------------------------------------------------- /entities/setup.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "github.com/ortizdavid/golang-fiber-webapp/config" 4 | 5 | func SetupMigrations() { 6 | db, _ := config.ConnectDB() 7 | db.AutoMigrate(&Role{}) 8 | db.AutoMigrate(&User{}) 9 | db.AutoMigrate(&TaskComplexity{}) 10 | db.AutoMigrate(&TaskStatus{}) 11 | db.AutoMigrate(&Task{}) 12 | } -------------------------------------------------------------------------------- /entities/task.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Task struct { 8 | TaskId int `gorm:"primaryKey;autoIncrement"` 9 | UserId int `gorm:"column:user_id;type:int"` 10 | StatusId int `gorm:"column:status_id;type:int"` 11 | ComplexityId int `gorm:"column:complexity_id;type:int"` 12 | TaskName string `gorm:"column:task_name;type:varchar(100)"` 13 | StartDate string `gorm:"column:start_date;type:date"` 14 | EndDate string `gorm:"column:end_date;type:date"` 15 | Description string `gorm:"column:description;type:varchar(300)"` 16 | Attachment string `gorm:"column:attachment;type:varchar(100)"` 17 | UniqueId string `gorm:"column:unique_id;type:varchar(50)"` 18 | CreatedAt time.Time `gorm:"column:created_at;type:datetime"` 19 | UpdatedAt time.Time `gorm:"column:updated_at;type:datetime"` 20 | } 21 | 22 | func (task Task) TableName() string { 23 | return "tasks" 24 | } 25 | 26 | type TaskData struct { 27 | TaskId int 28 | UniqueId string 29 | TaskName string 30 | StartDate string 31 | EndDate string 32 | Description string 33 | Attachment string 34 | CreatedAt string 35 | UpdatedAt string 36 | UserId int 37 | UserName string 38 | StatusId int 39 | StatusName string 40 | StatusCode string 41 | ComplexityId int 42 | ComplexityName string 43 | } 44 | 45 | -------------------------------------------------------------------------------- /entities/task_complexity.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type TaskComplexity struct { 4 | ComplexityId int `gorm:"primaryKey;autoIncrement"` 5 | ComplexityName string `gorm:"column:complexity_name;type:varchar(100)"` 6 | Code string `gorm:"column:code;type:varchar(20)"` 7 | } 8 | 9 | func (TaskComplexity) TableName() string { 10 | return "task_complexity" 11 | } -------------------------------------------------------------------------------- /entities/task_status.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type TaskStatus struct { 4 | StatusId int `gorm:"primaryKey;autoIncrement"` 5 | StatusName string `gorm:"column:status_name;type:varchar(100)"` 6 | Code string `gorm:"column:code;type:varchar(20)"` 7 | } 8 | 9 | func (TaskStatus) TableName() string { 10 | return "task_status" 11 | } -------------------------------------------------------------------------------- /entities/user.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | type User struct { 6 | UserId int `gorm:"autoIncrement;primarykey"` 7 | RoleId int `gorm:"column:role_id;type:int"` 8 | UserName string `gorm:"column:user_name;type:varchar(100)"` 9 | Password string `gorm:"column:password;type:varchar(150)"` 10 | Active string `gorm:"column:active;type:enum('Yes', 'No')"` 11 | Image string `gorm:"column:image;type:varchar(100)"` 12 | UniqueId string `gorm:"column:unique_id;type:varchar(50)"` 13 | Token string `gorm:"column:token;type:varchar(150)"` 14 | CreatedAt time.Time `gorm:"column:created_at"` 15 | UpdatedAt time.Time `gorm:"column:updated_at"` 16 | } 17 | 18 | func TableName() string { 19 | return "users" 20 | } 21 | 22 | type UserData struct { 23 | 24 | UserId int 25 | UniqueId string 26 | Token string 27 | UserName string 28 | Password string 29 | Active string 30 | Image string 31 | CreatedAtStr string 32 | UpdatedAtStr string 33 | RoleId int 34 | RoleName string 35 | RoleCode string 36 | } 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ortizdavid/golang-fiber-webapp 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.1 7 | github.com/gofiber/fiber/v2 v2.47.0 8 | github.com/gofiber/storage/mysql v1.3.7 9 | github.com/gofiber/template/html/v2 v2.0.4 10 | github.com/google/uuid v1.3.0 11 | github.com/joho/godotenv v1.5.1 12 | golang.org/x/crypto v0.13.0 13 | gorm.io/driver/mysql v1.5.1 14 | gorm.io/gorm v1.25.2 15 | ) 16 | 17 | require ( 18 | github.com/stretchr/testify v1.8.2 // indirect 19 | go.uber.org/multierr v1.10.0 // indirect 20 | ) 21 | 22 | require ( 23 | github.com/andybalholm/brotli v1.0.5 // indirect 24 | github.com/go-sql-driver/mysql v1.7.1 // indirect 25 | github.com/gofiber/template v1.8.2 // indirect 26 | github.com/gofiber/utils v1.1.0 // indirect 27 | github.com/jinzhu/inflection v1.0.0 // indirect 28 | github.com/jinzhu/now v1.1.5 // indirect 29 | github.com/klauspost/compress v1.16.3 // indirect 30 | github.com/mattn/go-colorable v0.1.13 // indirect 31 | github.com/mattn/go-isatty v0.0.19 // indirect 32 | github.com/mattn/go-runewidth v0.0.14 // indirect 33 | github.com/philhofer/fwd v1.1.2 // indirect 34 | github.com/rivo/uniseg v0.2.0 // indirect 35 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect 36 | github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect 37 | github.com/tinylib/msgp v1.1.8 // indirect 38 | github.com/valyala/bytebufferpool v1.0.0 // indirect 39 | github.com/valyala/fasthttp v1.47.0 // indirect 40 | github.com/valyala/tcplisten v1.0.0 // indirect 41 | go.uber.org/zap v1.26.0 42 | golang.org/x/sys v0.12.0 // indirect 43 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 44 | ) 45 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.1 h1:Jjo2fL1ByctCHRP99RGohe7ESvupcbRO/2E8Ps3ZcSw= 2 | github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.1/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ= 3 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= 4 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 9 | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= 10 | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 11 | github.com/gofiber/fiber/v2 v2.47.0 h1:EN5lHVCc+Pyqh5OEsk8fzRiifgwpbrP0rulQ4iNf3fs= 12 | github.com/gofiber/fiber/v2 v2.47.0/go.mod h1:mbFMVN1lQuzziTkkakgtKKdjfsXSw9BKR5lmcNksUoU= 13 | github.com/gofiber/storage/mysql v1.3.7 h1:yGYI7nnzaTE4o+nu3I4B1bL7KBTOolCjQMjCiWmdqz0= 14 | github.com/gofiber/storage/mysql v1.3.7/go.mod h1:I4oWq/j6rp2mXkQwzQMJLhXLKupJzAJLlDDf5zm0ETo= 15 | github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk= 16 | github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= 17 | github.com/gofiber/template/html/v2 v2.0.4 h1:exZDo23hrYRqxoX4vPkJj31yxXTmlFEu2aHd+XmJmLM= 18 | github.com/gofiber/template/html/v2 v2.0.4/go.mod h1:RCF14eLeQDCSUPp0IGc2wbSSDv6yt+V54XB/+Unz+LM= 19 | github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= 20 | github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= 21 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 22 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 23 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 24 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 25 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 26 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 27 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 28 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 29 | github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= 30 | github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 31 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 32 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 33 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 34 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 35 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 36 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 37 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 38 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 39 | github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= 40 | github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= 41 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 43 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 44 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 45 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= 46 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= 47 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= 48 | github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= 49 | github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= 50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 51 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 52 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 53 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 54 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 55 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 56 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 57 | github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= 58 | github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= 59 | github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= 60 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 61 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 62 | github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= 63 | github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= 64 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 65 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 66 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 67 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 68 | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 69 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 70 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 71 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 72 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 73 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 74 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 75 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 76 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 77 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 78 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 79 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 80 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 81 | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 82 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 83 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 84 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 85 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 86 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 87 | golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= 88 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 89 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 90 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 91 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 92 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 93 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 94 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 95 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 96 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 97 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 98 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 99 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 100 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 102 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 103 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 104 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 105 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 106 | golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= 107 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 108 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 109 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 110 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 111 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 112 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 113 | golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 114 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 115 | golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= 116 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 117 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 118 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 119 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 120 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 121 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 122 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 123 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 124 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 125 | gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= 126 | gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= 127 | gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 128 | gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= 129 | gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 130 | -------------------------------------------------------------------------------- /helpers/constants.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | const ( 4 | BYTE = 1 5 | KILO_BYTE = BYTE * 1024 6 | MEGA_BYTE = KILO_BYTE * 1024 7 | GIGA_BYTE = MEGA_BYTE * 1024 8 | TERA_BYTE = MEGA_BYTE * 1024 9 | ZETA_BYTE = TERA_BYTE * 1024 10 | PETA_BYTE = ZETA_BYTE * 1024 11 | ) -------------------------------------------------------------------------------- /helpers/email_service.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "fmt" 5 | "net/smtp" 6 | ) 7 | 8 | type EmailService struct { 9 | SMTPHost string 10 | SMTPPort int 11 | Username string 12 | Password string 13 | } 14 | 15 | func NewEmailService(smtpHost string, smtpPort int, username string, password string) *EmailService { 16 | return &EmailService{ 17 | SMTPHost: smtpHost, 18 | SMTPPort: smtpPort, 19 | Username: username, 20 | Password: password, 21 | } 22 | } 23 | 24 | func (es *EmailService) SendPlainEmail(to, subject, body string) error { 25 | message := fmt.Sprintf("To: %s\r\nSubject: %s\r\n\r\n%s", to, subject, body) 26 | return es.sendEmail(to, message) 27 | } 28 | 29 | 30 | func (es *EmailService) SendHTMLEmail(to, subject, bodyHTML string) error { 31 | message := fmt.Sprintf("To: %s\r\nSubject: %s\r\nContent-Type: text/html\r\n\r\n%s", to, subject, bodyHTML) 32 | return es.sendEmail(to, message) 33 | } 34 | 35 | func (es *EmailService) sendEmail(to, message string) error { 36 | auth := smtp.PlainAuth("", es.Username, es.Password, es.SMTPHost) 37 | addr := fmt.Sprintf("%s:%d", es.SMTPHost, es.SMTPPort) 38 | err := smtp.SendMail(addr, auth, es.Username, []string{to}, []byte(message)) 39 | if err != nil { 40 | return fmt.Errorf("error sending email: %v", err) 41 | } 42 | return nil 43 | } -------------------------------------------------------------------------------- /helpers/file_uploader.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "log" 7 | "strconv" 8 | "strings" 9 | "path/filepath" 10 | "github.com/gofiber/fiber/v2" 11 | ) 12 | 13 | // Upload a file 14 | func UploadFile(ctx *fiber.Ctx, formFile string, fileType string, savePath string) (string, error) { 15 | file, err := ctx.FormFile(formFile) 16 | if err != nil { 17 | return "", ctx.Status(fiber.StatusBadRequest).SendString(err.Error()) 18 | } 19 | fileName := file.Filename 20 | fileExtension := getFileExtension(fileName) 21 | extensions := extensionsByFileType(fileType) 22 | if !isValidExtension(fileExtension, extensions) { 23 | return "", ctx.Status(fiber.StatusBadRequest).SendString("Invalid file extension. Only " +fileType) 24 | } 25 | newFileName := generateUniqueFileName(fileName) 26 | saveFilePath := filepath.Join(savePath, newFileName) 27 | err = ctx.SaveFile(file, saveFilePath) 28 | log.Println("File: ", newFileName) 29 | if err != nil { 30 | return "", ctx.Status(fiber.StatusInternalServerError).SendString("Error saving the file") 31 | } 32 | return newFileName, nil 33 | } 34 | 35 | func extensionsByFileType(fileType string) []string { 36 | var extensions []string 37 | switch fileType { 38 | case "document": 39 | extensions = []string{".pdf"} 40 | case "image": 41 | extensions = []string{".jpg", ".jpeg", ".png", ".gif"} 42 | } 43 | return extensions 44 | } 45 | 46 | func generateUniqueFileName(originalName string) string { 47 | extension := getFileExtension(originalName) 48 | timestamp := time.Now().Unix() 49 | random := strconv.FormatInt(time.Now().UnixNano(), 10)[10:] 50 | return fmt.Sprintf("%d%s%s", timestamp, random, extension) 51 | } 52 | 53 | func getFileExtension(originalFileName string) string { 54 | return strings.ToLower(filepath.Ext(originalFileName)) 55 | } 56 | 57 | func isValidExtension(fileExtension string, allowedExtensions []string) bool { 58 | valid := false 59 | for _, ext := range allowedExtensions { 60 | if ext == fileExtension { 61 | valid = true 62 | break 63 | } 64 | } 65 | return valid 66 | } -------------------------------------------------------------------------------- /helpers/functions.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | 5 | "fmt" 6 | "log" 7 | "time" 8 | "strconv" 9 | "crypto/rand" 10 | "encoding/base64" 11 | "github.com/google/uuid" 12 | ) 13 | 14 | func GenerateUUID() string { 15 | uniqueId := uuid.New() 16 | return uniqueId.String() 17 | } 18 | 19 | func GenerateCode(prefix string) string { 20 | timestamp := time.Now().Format("20060102150405") 21 | return prefix + timestamp 22 | } 23 | 24 | func GenerateRandomToken() string { 25 | length := 100 26 | randomBytes := make([]byte, length) 27 | _, err := rand.Read(randomBytes) 28 | if err != nil { 29 | log.Fatal(err) 30 | return "" 31 | } 32 | token := base64.RawURLEncoding.EncodeToString(randomBytes) 33 | return token 34 | } 35 | 36 | func ConvertToInt(value string) int { 37 | intValue, _ := strconv.Atoi(value) 38 | return intValue 39 | } 40 | 41 | func ConvertToString(value any) string { 42 | return fmt.Sprintf("%v", value) 43 | } -------------------------------------------------------------------------------- /helpers/html_pdf_generator.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | "github.com/gofiber/fiber/v2" 7 | "github.com/SebastiaanKlippert/go-wkhtmltopdf" 8 | ) 9 | 10 | type HtmlPdfGenerator struct { 11 | } 12 | 13 | func (gen *HtmlPdfGenerator) GeneratePDF(htmlTemplate string, data map[string]interface{}) ([]byte, error) { 14 | var buf bytes.Buffer 15 | // Load HTML template 16 | tmpl, err := gen.LoadHtmlTemplate(htmlTemplate) 17 | if err != nil { 18 | return nil, err 19 | } 20 | // Execute the template with the data 21 | err = tmpl.Execute(&buf, data) 22 | if err != nil { 23 | return nil, err 24 | } 25 | // Create new PDF generator 26 | pdfGen, err := wkhtmltopdf.NewPDFGenerator() 27 | if err != nil { 28 | return nil, err 29 | } 30 | // Set global options 31 | pdfGen.Dpi.Set(100) 32 | pdfGen.Orientation.Set(wkhtmltopdf.OrientationPortrait) 33 | pdfGen.Grayscale.Set(true) 34 | // Create a new input page from HTML content 35 | page := wkhtmltopdf.NewPageReader(&buf) 36 | // Set options for this page 37 | page.FooterRight.Set("[page]") 38 | page.FooterFontSize.Set(10) 39 | page.Zoom.Set(0.95) 40 | // Add to the document 41 | pdfGen.AddPage(page) 42 | // Create PDF document in the internal buffer 43 | err = pdfGen.Create() 44 | if err != nil { 45 | return nil, err 46 | } 47 | // Get the PDF bytes 48 | pdfBytes := pdfGen.Bytes() 49 | return pdfBytes, nil 50 | } 51 | 52 | func (gen *HtmlPdfGenerator) LoadHtmlTemplate(filePath string) (*template.Template, error) { 53 | tmpl, err := template.ParseFiles(filePath) 54 | return tmpl, err 55 | } 56 | 57 | func (gen *HtmlPdfGenerator) SetOutput(ctx *fiber.Ctx, pdfBytes []byte, fileName string) error { 58 | ctx.Response().Header.SetContentType("application/pdf") 59 | ctx.Response().Header.Set("Content-Disposition", "attachment; fileName=" +fileName) 60 | _, err := ctx.Write(pdfBytes) 61 | return err 62 | } 63 | -------------------------------------------------------------------------------- /helpers/pagination.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "strconv" 5 | "github.com/gofiber/fiber/v2" 6 | ) 7 | 8 | type PaginationRender struct { 9 | PageNumber int 10 | PrevPage int 11 | NextPage int 12 | } 13 | 14 | type Pagination struct { 15 | } 16 | 17 | func NewPaginationRender(pageNumber int) PaginationRender { 18 | return PaginationRender{ 19 | PageNumber: pageNumber, 20 | PrevPage: pageNumber - 1, 21 | NextPage: pageNumber + 1, 22 | } 23 | } 24 | 25 | func (p *Pagination) GetPageNumber( ctx *fiber.Ctx, pageQuery string) int { 26 | pageNumberStr := ctx.Query(pageQuery) 27 | pageNumber, err := strconv.Atoi(pageNumberStr) 28 | if err != nil || pageNumber < 1 { 29 | pageNumber = 1 30 | } 31 | return pageNumber 32 | } 33 | 34 | func (p *Pagination) CalculateTotalPages(totalRecords int, itemsPerPage int) int { 35 | return (totalRecords + itemsPerPage - 1) / itemsPerPage 36 | } 37 | 38 | func (p *Pagination) CalculateStartIndex(pageNumber int, itemsPerPage int) int { 39 | return (pageNumber - 1) * itemsPerPage 40 | } 41 | -------------------------------------------------------------------------------- /helpers/password_hashing.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "log" 5 | "golang.org/x/crypto/bcrypt" 6 | ) 7 | 8 | func HashPassword(password string) string { 9 | hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 10 | if err != nil { 11 | log.Fatal(err) 12 | } 13 | return string(hash) 14 | } 15 | 16 | func CheckPassword(hashedPassword string, password string) bool { 17 | err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) 18 | return err == nil 19 | } 20 | -------------------------------------------------------------------------------- /helpers/time_date.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import "time" 4 | 5 | func CurrentDate() string { 6 | return time.Now().Format("2006-01-02") 7 | } 8 | 9 | func CurrentDateTime() string { 10 | return time.Now().Format("2006-01-02 15:04:05") 11 | } 12 | 13 | func StringToDate(strDate string) time.Time { 14 | date, _ := time.Parse("2006-01-02", strDate) 15 | return date 16 | } 17 | 18 | func StringToDateTime(strDateTime string) time.Time { 19 | dateTime, _ := time.Parse("2006-01-02 15:04:05", strDateTime) 20 | return dateTime 21 | } -------------------------------------------------------------------------------- /logs/auth-logs.log: -------------------------------------------------------------------------------- 1 | {"level":"error","ts":1697538261.412495,"msg":"User '' failed to login"} 2 | {"level":"error","ts":1697538263.6620102,"msg":"User '' failed to login"} 3 | {"level":"error","ts":1697538264.2320356,"msg":"User '' failed to login"} 4 | {"level":"error","ts":1697538265.8017151,"msg":"User 'aq' failed to login"} 5 | {"level":"info","ts":1698140686.4169536,"msg":"User 'ortizaad1994@gmail.com' recovered password","path":"/auth/get-recovery-link"} 6 | {"level":"info","ts":1698140723.6579967,"msg":"User '' recovered password","path":"/auth/recover-password/"} 7 | {"level":"info","ts":1698142192.1143992,"msg":"User 'ortizaad1994@gmail.com' recovered password","path":"/auth/get-recovery-link"} 8 | {"level":"info","ts":1698142345.433561,"msg":"User 'ortizaad1994@gmail.com' recovered password","path":"/auth/get-recovery-link"} 9 | {"level":"info","ts":1698142364.949367,"msg":"User 'admin@gmail.com' recovered password","path":"/auth/get-recovery-link"} 10 | {"level":"info","ts":1698142519.7852492,"msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 11 | {"level":"info","ts":1698142687.6313949,"msg":"User 'ortizaad1994@gmail.com' logged out"} 12 | {"level":"info","ts":1698142695.6540234,"msg":"User 'ortizaad1994@gmail.com' recovered password","path":"/auth/get-recovery-link"} 13 | {"level":"info","ts":1698142839.448223,"msg":"User 'ortizaad1994@gmail.com' recovered password","path":"/auth/get-recovery-link"} 14 | {"level":"info","ts":1698222191.219316,"msg":"User 'ortizaad1994@gmail.com' logged out"} 15 | {"level":"error","ts":1698222191.407357,"msg":"Authentication failed at: /image/golang-logo.png"} 16 | {"level":"error","ts":1698222312.476223,"msg":"Authentication failed at: /image/golang-logo.png"} 17 | {"level":"error","ts":1698222315.3180048,"msg":"Authentication failed at: /image/golang-logo.png"} 18 | {"level":"error","ts":1698222316.6292748,"msg":"Authentication failed at: /image/golang-logo.png"} 19 | {"level":"error","ts":1698222318.600103,"msg":"Authentication failed at: /image/golang-logo.png"} 20 | {"level":"error","ts":1698234926.3483334,"msg":"Authentication failed at: /image/golang-logo.png"} 21 | {"level":"error","ts":1698234926.366826,"msg":"Authentication failed at: /favicon.ico"} 22 | {"level":"error","ts":1698234929.0940928,"msg":"User '' failed to login"} 23 | {"level":"error","ts":1698234929.1114087,"msg":"Authentication failed at: /image/golang-logo.png"} 24 | {"level":"error","ts":1698234929.139806,"msg":"Authentication failed at: /favicon.ico"} 25 | {"level":"error","ts":1698243139.014679,"msg":"Authentication failed at: /image/golang-logo.png"} 26 | {"level":"error","ts":1698243139.014679,"msg":"Authentication failed at: /js/jquery-3.6.1.min.js"} 27 | {"level":"error","ts":1698243139.014679,"msg":"Authentication failed at: /css/bootstrap.min.css"} 28 | {"level":"error","ts":1698243139.05507,"msg":"Authentication failed at: /favicon.ico"} 29 | {"level":"error","ts":1698243143.200784,"msg":"Authentication failed at: /js/jquery-3.6.1.min.js"} 30 | {"level":"error","ts":1698243143.200784,"msg":"Authentication failed at: /image/golang-logo.png"} 31 | {"level":"error","ts":1698243143.200784,"msg":"Authentication failed at: /css/bootstrap.min.css"} 32 | {"level":"error","ts":1698243143.221882,"msg":"Authentication failed at: /favicon.ico"} 33 | {"level":"error","ts":1698243143.9240324,"msg":"Authentication failed at: /js/jquery-3.6.1.min.js"} 34 | {"level":"error","ts":1698243143.9240324,"msg":"Authentication failed at: /css/bootstrap.min.css"} 35 | {"level":"error","ts":1698243143.9240324,"msg":"Authentication failed at: /image/golang-logo.png"} 36 | {"level":"error","ts":1698243143.9502022,"msg":"Authentication failed at: /favicon.ico"} 37 | {"level":"info","ts":1698243157.007812,"msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 38 | {"level":"error","ts":1699610300.0126529,"msg":"Authentication failed at: /js/jquery-3.6.1.min.js"} 39 | {"level":"error","ts":1699610300.0126529,"msg":"Authentication failed at: /css/bootstrap.min.css"} 40 | {"level":"error","ts":1699610300.0126529,"msg":"Authentication failed at: /image/golang-logo.png"} 41 | {"level":"error","ts":1699610300.0534134,"msg":"Authentication failed at: /favicon.ico"} 42 | {"level":"error","ts":1699610308.634095,"msg":"Authentication failed at: /js/jquery-3.6.1.min.js"} 43 | {"level":"error","ts":1699610308.6359,"msg":"Authentication failed at: /css/bootstrap.min.css"} 44 | {"level":"error","ts":1699610308.6424441,"msg":"Authentication failed at: /image/golang-logo.png"} 45 | {"level":"error","ts":1699610308.66491,"msg":"Authentication failed at: /favicon.ico"} 46 | {"level":"info","ts":1699610317.063581,"msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 47 | {"level":"info","ts":1699610402.6875813,"msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 48 | {"level":"info","ts":"2023-11-10T11:21:10.523+0100","msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 49 | {"level":"info","ts":"2023-11-10T11:25:48.674+0100","msg":"User 'ortizaad1994@gmail.com' logged out"} 50 | {"level":"error","ts":"2023-11-10T11:25:48.703+0100","msg":"Authentication failed at: /image/golang-logo.png"} 51 | {"level":"info","ts":"2023-11-10T11:26:00.112+0100","msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 52 | {"level":"info","ts":"2023-11-10T11:26:46.958+0100","msg":"User 'ortizaad1994@gmail.com' logged out"} 53 | {"level":"error","ts":"2023-11-10T11:26:46.993+0100","msg":"Authentication failed at: /image/golang-logo.png"} 54 | {"level":"info","ts":"2023-11-10T11:26:54.290+0100","msg":"User 'normalgmail.com' authenticated sucessfully!"} 55 | {"level":"info","ts":"2023-11-10T14:12:15.912+0100","msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 56 | {"level":"info","ts":"2023-11-10T14:18:04.701+0100","msg":"User 'ortizaad1994@gmail.com' logged out"} 57 | {"level":"error","ts":"2023-11-10T14:18:04.731+0100","msg":"Authentication failed at: /image/golang-logo.png"} 58 | {"level":"error","ts":"2023-11-10T14:18:16.857+0100","msg":"Authentication failed at: /tasks"} 59 | {"level":"error","ts":"2023-11-10T14:18:16.883+0100","msg":"Authentication failed at: /image/golang-logo.png"} 60 | {"level":"error","ts":"2023-11-10T14:18:57.449+0100","msg":"Authentication failed at: /tasks"} 61 | {"level":"error","ts":"2023-11-10T14:18:57.472+0100","msg":"Authentication failed at: /image/golang-logo.png"} 62 | {"level":"info","ts":"2023-11-10T14:23:46.759+0100","msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 63 | {"level":"info","ts":"2023-11-10T14:23:52.246+0100","msg":"User 'ortizaad1994@gmail.com' logged out"} 64 | {"level":"error","ts":"2023-11-20T14:28:25.058+0100","msg":"Authentication failed at: /favicon.ico"} 65 | {"level":"info","ts":"2023-11-20T14:28:32.667+0100","msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 66 | {"level":"info","ts":"2023-11-20T14:29:02.517+0100","msg":"User 'ortizaad1994@gmail.com' logged out"} 67 | {"level":"info","ts":"2023-11-20T14:30:26.900+0100","msg":"User 'normalgmail.com' authenticated sucessfully!"} 68 | {"level":"info","ts":"2023-11-20T14:36:49.653+0100","msg":"User 'normalgmail.com' logged out"} 69 | {"level":"error","ts":"2023-11-20T14:37:13.125+0100","msg":"User 'normal@gmail.com' failed to login"} 70 | {"level":"error","ts":"2023-11-20T14:37:25.853+0100","msg":"User 'normal@gmail.com' failed to login"} 71 | {"level":"error","ts":"2023-11-20T14:37:37.357+0100","msg":"User 'normal@user.com' failed to login"} 72 | {"level":"info","ts":"2023-11-20T14:37:53.840+0100","msg":"User 'normalgmail.com' authenticated sucessfully!"} 73 | {"level":"info","ts":"2023-11-20T14:46:38.409+0100","msg":"User 'normalgmail.com' authenticated sucessfully!"} 74 | {"level":"info","ts":"2023-11-20T14:48:12.509+0100","msg":"User 'normalgmail.com' logged out"} 75 | {"level":"info","ts":"2023-11-20T14:48:18.517+0100","msg":"User 'ortizaad1994@gmail.com' authenticated sucessfully!"} 76 | {"level":"info","ts":"2023-11-20T14:48:58.616+0100","msg":"User 'ortizaad1994@gmail.com' logged out"} 77 | {"level":"info","ts":"2023-11-20T14:49:06.981+0100","msg":"User 'test@user.com' authenticated sucessfully!"} 78 | {"level":"error","ts":"2023-11-20T14:54:19.313+0100","msg":"Authentication failed at: /tasks/my-tasks"} 79 | {"level":"info","ts":"2023-11-20T14:54:34.589+0100","msg":"User 'admin@gmail.com' authenticated sucessfully!"} 80 | {"level":"info","ts":"2023-11-20T14:57:34.862+0100","msg":"User 'admin@gmail.com' logged out"} 81 | {"level":"info","ts":"2023-11-20T14:57:50.334+0100","msg":"User 'normal@gmail.com' authenticated sucessfully!"} 82 | {"level":"info","ts":"2023-11-20T14:58:09.209+0100","msg":"User 'normal@gmail.com' logged out"} 83 | {"level":"info","ts":"2023-11-20T14:58:20.277+0100","msg":"User 'admin@gmail.com' authenticated sucessfully!"} 84 | {"level":"info","ts":"2023-11-20T14:58:44.791+0100","msg":"User 'admin@gmail.com' logged out"} 85 | {"level":"info","ts":"2023-11-20T14:58:53.322+0100","msg":"User 'normal@gmail.com' authenticated sucessfully!"} 86 | {"level":"info","ts":"2023-11-20T14:59:13.394+0100","msg":"User 'normal@gmail.com' logged out"} 87 | {"level":"info","ts":"2023-11-20T14:59:21.166+0100","msg":"User 'admin@gmail.com' authenticated sucessfully!"} 88 | {"level":"info","ts":"2023-11-20T14:59:53.504+0100","msg":"User 'admin@gmail.com' logged out"} 89 | {"level":"info","ts":"2023-11-20T15:00:03.034+0100","msg":"User 'test@user.com' authenticated sucessfully!"} 90 | {"level":"error","ts":"2023-11-21T16:13:31.054+0100","msg":"Authentication failed at: /favicon.ico"} 91 | {"level":"info","ts":"2023-11-21T16:14:44.183+0100","msg":"User 'admin@gmail.com' authenticated sucessfully!"} 92 | {"level":"info","ts":"2023-11-21T16:14:55.366+0100","msg":"User 'admin@gmail.com' logged out"} 93 | {"level":"info","ts":"2023-11-21T16:15:04.234+0100","msg":"User 'normal@gmail.com' authenticated sucessfully!"} 94 | {"level":"info","ts":"2023-11-21T16:35:30.647+0100","msg":"User 'normal@gmail.com' logged out"} 95 | {"level":"info","ts":"2023-11-21T16:35:37.094+0100","msg":"User 'admin@gmail.com' authenticated sucessfully!"} 96 | {"level":"info","ts":"2023-11-21T16:38:42.862+0100","msg":"User 'admin@gmail.com' logged out"} 97 | {"level":"info","ts":"2023-11-21T16:38:56.505+0100","msg":"User 'admin@gmail.com' authenticated sucessfully!"} 98 | {"level":"info","ts":"2023-11-21T16:39:05.549+0100","msg":"User 'admin@gmail.com' logged out"} 99 | {"level":"info","ts":"2023-11-21T16:39:13.007+0100","msg":"User 'normal@gmail.com' authenticated sucessfully!"} 100 | {"level":"error","ts":"2023-11-22T08:04:33.724+0100","msg":"User 'ortizaad1994@gmail.com' failed to login"} 101 | {"level":"info","ts":"2023-11-22T08:04:49.012+0100","msg":"User 'normal@gmail.com' authenticated sucessfully!"} 102 | {"level":"info","ts":"2023-11-22T10:36:36.101+0100","msg":"User 'admin@gmail.com' authenticated sucessfully!"} 103 | {"level":"info","ts":"2023-11-22T11:03:43.110+0100","msg":"User 'admin@gmail.com' logged out"} 104 | {"level":"info","ts":"2023-11-22T11:03:59.208+0100","msg":"User 'normal@gmail.com' authenticated sucessfully!"} 105 | {"level":"info","ts":"2023-11-22T11:48:46.667+0100","msg":"User 'normal@gmail.com' authenticated sucessfully!"} 106 | {"level":"info","ts":"2023-11-22T11:49:01.396+0100","msg":"User 'normal@gmail.com' logged out"} 107 | {"level":"info","ts":"2023-11-22T11:49:08.384+0100","msg":"User 'test@user.com' authenticated sucessfully!"} 108 | {"level":"error","ts":"2024-01-16T13:06:53.393+0100","msg":"Authentication failed at: /favicon.ico"} 109 | {"level":"error","ts":"2024-01-16T13:07:04.042+0100","msg":"User 'ortizaad1994@gmail.com' failed to login"} 110 | {"level":"error","ts":"2024-01-16T13:07:04.105+0100","msg":"Authentication failed at: /favicon.ico"} 111 | {"level":"error","ts":"2024-01-16T13:07:05.630+0100","msg":"User '' failed to login"} 112 | {"level":"error","ts":"2024-01-16T13:07:05.676+0100","msg":"Authentication failed at: /favicon.ico"} 113 | {"level":"info","ts":"2024-01-16T13:07:12.573+0100","msg":"User 'admin@gmail.com' authenticated sucessfully!"} 114 | {"level":"info","ts":"2024-01-16T13:07:14.555+0100","msg":"User 'admin@gmail.com' logged out"} 115 | {"level":"error","ts":"2024-01-16T14:11:21.184+0100","msg":"User '' failed to login"} 116 | {"level":"error","ts":"2024-01-16T14:11:22.427+0100","msg":"User '' failed to login"} 117 | {"level":"error","ts":"2024-01-18T10:23:06.732+0100","msg":"Authentication failed at: /favicon.ico"} 118 | {"level":"error","ts":"2024-01-18T10:23:10.565+0100","msg":"Authentication failed at: /favicon.ico"} 119 | {"level":"error","ts":"2024-07-16T14:55:12.979+0100","msg":"Authentication failed at: /favicon.ico"} 120 | {"level":"info","ts":"2024-07-16T14:55:22.266+0100","msg":"User 'admin@gmail.com' authenticated sucessfully!"} 121 | -------------------------------------------------------------------------------- /logs/report-logs.log: -------------------------------------------------------------------------------- 1 | {"level":"info","ts":1698142527.2815251,"msg":"User 'ortizaad1994@gmail.com' generated 'Users' Report"} 2 | {"level":"info","ts":1698142534.9217591,"msg":"User 'ortizaad1994@gmail.com' generated 'Completed Tasks' Report"} 3 | {"level":"info","ts":1698142542.9425225,"msg":"User 'ortizaad1994@gmail.com' generated 'Pending Tasks' Report"} 4 | {"level":"info","ts":1698142680.9754434,"msg":"User 'ortizaad1994@gmail.com' generated 'Users' Report"} 5 | {"level":"info","ts":1698221739.0716195,"msg":"User 'ortizaad1994@gmail.com' generated 'Users' Report"} 6 | {"level":"info","ts":1699610327.5045,"msg":"User 'ortizaad1994@gmail.com' generated 'Users' Report"} 7 | {"level":"info","ts":"2023-11-10T11:24:51.645+0100","msg":"User 'ortizaad1994@gmail.com' generated 'Completed Tasks' Report"} 8 | -------------------------------------------------------------------------------- /logs/task-logs.log: -------------------------------------------------------------------------------- 1 | {"level":"info","ts":"2023-11-10T11:28:45.356+0100","msg":"User 'normalgmail.com' added Task ''"} 2 | {"level":"info","ts":"2023-11-10T11:31:08.194+0100","msg":"User 'normalgmail.com' added Task 'dddddd'"} 3 | {"level":"info","ts":"2023-11-20T14:47:40.300+0100","msg":"User 'normalgmail.com' added Task 'Testing App'"} 4 | {"level":"info","ts":"2023-11-20T15:00:23.461+0100","msg":"User 'test@user.com' added Task 'Testing App'"} 5 | {"level":"info","ts":"2023-11-21T16:39:36.048+0100","msg":"User 'normal@gmail.com' added Task 'Testing App'"} 6 | {"level":"info","ts":"2023-11-22T08:05:21.285+0100","msg":"User 'normal@gmail.com' added Task 'Testing App'"} 7 | {"level":"info","ts":"2023-11-22T08:20:04.019+0100","msg":"User 'normal@gmail.com' added Task 'Testing App'"} 8 | {"level":"info","ts":"2023-11-22T11:05:04.833+0100","msg":"User 'normal@gmail.com' added Task 'Testing App'"} 9 | {"level":"info","ts":"2023-11-22T11:49:45.038+0100","msg":"User 'test@user.com' added Task 'Writing Docs'"} 10 | {"level":"info","ts":"2023-11-22T12:21:32.830+0100","msg":"User 'test@user.com' Uploaded Task from CSV File 'tasks_for_upload.csv'"} 11 | -------------------------------------------------------------------------------- /logs/user-logs.log: -------------------------------------------------------------------------------- 1 | {"level":"info","ts":1698221976.0951147,"msg":"User 'admin@gmail.com' deactivated successfuly"} 2 | {"level":"info","ts":1698221993.0730042,"msg":"User 'ortizaad1994@gmail.com' added image"} 3 | {"level":"info","ts":1698243252.9383266,"msg":"User '' added successfully"} 4 | {"level":"info","ts":"2023-11-10T11:25:03.831+0100","msg":"User 'ortizaad1994@gmail.com' Searched for User 's' and Found 0 results"} 5 | {"level":"info","ts":"2023-11-10T11:25:11.158+0100","msg":"User 'ortizaad1994@gmail.com' Searched for User 'ortizaad1994@gmail.com' and Found 1 results"} 6 | {"level":"info","ts":"2023-11-10T11:26:35.304+0100","msg":"User 'normalgmail.com' added successfully"} 7 | {"level":"info","ts":"2023-11-20T14:48:55.490+0100","msg":"User 'test@user.com' added successfully"} 8 | {"level":"info","ts":"2023-11-20T14:55:39.277+0100","msg":"User 'normal@gmail.com' added successfully"} 9 | {"level":"info","ts":"2023-11-20T14:58:38.697+0100","msg":"User 'normal@gmail.com' Updated successfully"} 10 | {"level":"info","ts":"2023-11-20T14:59:50.536+0100","msg":"User 'test@user.com' added successfully"} 11 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/ortizdavid/golang-fiber-webapp/config" 6 | "github.com/ortizdavid/golang-fiber-webapp/controllers" 7 | ) 8 | 9 | func main() { 10 | 11 | app := fiber.New(fiber.Config{ 12 | Views: config.GetTemplateEngine(), 13 | }) 14 | 15 | //Middlewares 16 | app.Use(controllers.RequestLoggerMiddleware) 17 | app.Use(controllers.AuthenticationMiddleware) 18 | 19 | config.ConfigStaticFiles(app) 20 | controllers.SetupRoutes(app) 21 | app.Listen(config.ListenAddr()) 22 | } -------------------------------------------------------------------------------- /models/csv_parser_task.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "io" 5 | "fmt" 6 | "time" 7 | "encoding/csv" 8 | "github.com/ortizdavid/golang-fiber-webapp/entities" 9 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 10 | ) 11 | 12 | const ( 13 | statusIndex = 0 14 | complexityIndex = 1 15 | taskNameIndex = 2 16 | startDateIndex = 3 17 | endDateIndex = 4 18 | descriptionIndex = 5 19 | ) 20 | 21 | func ParseTaskFromCSV(reader *csv.Reader, userId int) ([]entities.Task, error) { 22 | var tasks []entities.Task 23 | var lineNumber int 24 | 25 | for { 26 | record, err := reader.Read() 27 | lineNumber++ 28 | 29 | if err == io.EOF { 30 | break 31 | } else if err != nil { 32 | return nil, fmt.Errorf("error reading CSV record %v on line %d", err, lineNumber) 33 | } 34 | 35 | if len(record) != 6 { 36 | return nil, fmt.Errorf("invalid CSV record %v", err) 37 | } 38 | 39 | task := entities.Task { 40 | TaskId: 0, 41 | UserId: userId, 42 | StatusId: helpers.ConvertToInt(record[statusIndex]), 43 | ComplexityId: helpers.ConvertToInt(record[complexityIndex]), 44 | TaskName: record[taskNameIndex], 45 | StartDate: record[startDateIndex], 46 | EndDate: record[endDateIndex], 47 | Description: record[descriptionIndex], 48 | Attachment: "", 49 | UniqueId: helpers.GenerateUUID(), 50 | CreatedAt: time.Now(), 51 | UpdatedAt: time.Now(), 52 | } 53 | tasks = append(tasks, task) 54 | } 55 | return tasks, nil 56 | } 57 | 58 | 59 | func SkipCSVHeader(reader *csv.Reader) error { 60 | _, err := reader.Read() 61 | return err 62 | } -------------------------------------------------------------------------------- /models/report_model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type ReportModel struct { 4 | } 5 | 6 | type TableReport struct { 7 | Title string 8 | Rows interface{} 9 | Count int 10 | } 11 | 12 | func (ReportModel) GetAllUsers() TableReport { 13 | rows, _ := UserModel{}.FindAllData() 14 | count := len(rows) 15 | return TableReport { 16 | Title: "Users", 17 | Rows: rows, 18 | Count: count, 19 | } 20 | } 21 | 22 | func (ReportModel) GetAllActiveUsers() TableReport { 23 | rows, _ := UserModel{}.FindAllByStatus("Yes") 24 | count := len(rows) 25 | return TableReport { 26 | Title: "Active Users", 27 | Rows: rows, 28 | Count: count, 29 | } 30 | } 31 | 32 | func (ReportModel) GetAllTasks() TableReport { 33 | rows, _ := TaskModel{}.FindAllData() 34 | count := len(rows) 35 | return TableReport { 36 | Title: "Tasks", 37 | Rows: rows, 38 | Count: count, 39 | } 40 | } 41 | 42 | func (ReportModel) GetAllPendingTasks() TableReport { 43 | rows, _ := TaskModel{}.FindAllDataByStatus("pending") 44 | count := len(rows) 45 | return TableReport { 46 | Title: "Pending Tasks", 47 | Rows: rows, 48 | Count: count, 49 | } 50 | } 51 | 52 | func (ReportModel) GetAllCompletedTasks() TableReport { 53 | rows, _ := TaskModel{}.FindAllDataByStatus("completed") 54 | count := len(rows) 55 | return TableReport { 56 | Title: "Completed Tasks", 57 | Rows: rows, 58 | Count: count, 59 | } 60 | } 61 | 62 | func (ReportModel) GetAllInProgressTasks() TableReport { 63 | rows, _ := TaskModel{}.FindAllDataByStatus("in-progress") 64 | count := len(rows) 65 | return TableReport { 66 | Title: "In Progress", 67 | Rows: rows, 68 | Count: count, 69 | } 70 | } 71 | 72 | func (ReportModel) GetAllBlockedTasks() TableReport { 73 | rows, _ := TaskModel{}.FindAllDataByStatus("blocked") 74 | count := len(rows) 75 | return TableReport { 76 | Title: "Blocked Tasks", 77 | Rows: rows, 78 | Count: count, 79 | } 80 | } 81 | 82 | func (ReportModel) GetAllCancelledTasks() TableReport { 83 | rows, _ := TaskModel{}.FindAllDataByStatus("canceled") 84 | count := len(rows) 85 | return TableReport { 86 | Title: "Cancelled Tasks", 87 | Rows: rows, 88 | Count: count, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /models/role_model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "github.com/ortizdavid/golang-fiber-webapp/config" 6 | "github.com/ortizdavid/golang-fiber-webapp/entities" 7 | ) 8 | 9 | type RoleModel struct { 10 | } 11 | 12 | 13 | func (RoleModel) Create(role entities.Role) (*gorm.DB, error) { 14 | db, _ := config.ConnectDB() 15 | defer config.DisconnectDB(db) 16 | result := db.Create(&role) 17 | if result.Error != nil { 18 | return nil, result.Error 19 | } 20 | return result, nil 21 | } 22 | 23 | func (RoleModel) FindAll() ([]entities.Role, error) { 24 | db, _ := config.ConnectDB() 25 | defer config.DisconnectDB(db) 26 | var roles []entities.Role 27 | result := db.Find(&roles) 28 | if result.Error != nil { 29 | return nil, result.Error 30 | } 31 | return roles, nil 32 | } 33 | 34 | func (RoleModel) Update(role entities.Role) (*gorm.DB, error) { 35 | db, _ := config.ConnectDB() 36 | defer config.DisconnectDB(db) 37 | result := db.Save(&role) 38 | if result.Error != nil { 39 | return nil, result.Error 40 | } 41 | return result, nil 42 | } 43 | 44 | func (RoleModel) FindById(id int) (entities.Role, error) { 45 | db, _ := config.ConnectDB() 46 | defer config.DisconnectDB(db) 47 | var role entities.Role 48 | result := db.First(&role, id) 49 | if result.Error != nil { 50 | return entities.Role{}, result.Error 51 | } 52 | return role, nil 53 | } 54 | 55 | func (RoleModel) FindByName(name string) (entities.Role, error) { 56 | db, _ := config.ConnectDB() 57 | defer config.DisconnectDB(db) 58 | var role entities.Role 59 | result := db.Where("role_name=?", name).First(&role) 60 | if result.Error != nil { 61 | return entities.Role{}, result.Error 62 | } 63 | return role, nil 64 | } 65 | 66 | func (RoleModel) Count() (int64, error) { 67 | db, _ := config.ConnectDB() 68 | defer config.DisconnectDB(db) 69 | var count int64 70 | result := db.Table("roles").Count(&count) 71 | if result.Error != nil { 72 | return 0, result.Error 73 | } 74 | return count, nil 75 | } 76 | -------------------------------------------------------------------------------- /models/statistic_model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type StatisticCount struct { 4 | Users int64 5 | Tasks int64 6 | PendingTasks int64 7 | CompletedTasks int64 8 | InProgressTasks int64 9 | BlockedTasks int64 10 | CanceledTasks int64 11 | } 12 | 13 | func GetStatisticsCount() StatisticCount { 14 | countUsers, _ := UserModel{}.Count() 15 | countTasks, _ := TaskModel{}.Count() 16 | countPending, _ := TaskModel{}.CountByStatus("pending") 17 | countCompleted, _ := TaskModel{}.CountByStatus("completed") 18 | countInProgress, _ := TaskModel{}.CountByStatus("in-progress") 19 | countBlocked, _ := TaskModel{}.CountByStatus("blocked") 20 | countCanceled, _ := TaskModel{}.CountByStatus("canceled") 21 | return StatisticCount{ 22 | Users: countUsers, 23 | Tasks: countTasks, 24 | PendingTasks: countPending, 25 | CompletedTasks: countCompleted, 26 | InProgressTasks: countInProgress, 27 | BlockedTasks: countBlocked, 28 | CanceledTasks: countCanceled, 29 | } 30 | } 31 | 32 | func GetStatisticsCountByUser(userId int) StatisticCount { 33 | countTasks, _ := TaskModel{}.CountByUser(userId) 34 | countPending, _ := TaskModel{}.CountByStatusAndUser("pending", userId) 35 | countCompleted, _ := TaskModel{}.CountByStatusAndUser("completed", userId) 36 | countInProgress, _ := TaskModel{}.CountByStatusAndUser("in-progress", userId) 37 | countBlocked, _ := TaskModel{}.CountByStatusAndUser("blocked", userId) 38 | countCanceled, _ := TaskModel{}.CountByStatusAndUser("canceled", userId) 39 | return StatisticCount{ 40 | Users: 0, 41 | Tasks: countTasks, 42 | PendingTasks: countPending, 43 | CompletedTasks: countCompleted, 44 | InProgressTasks: countInProgress, 45 | BlockedTasks: countBlocked, 46 | CanceledTasks: countCanceled, 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /models/task_complexity_model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "github.com/ortizdavid/golang-fiber-webapp/config" 6 | "github.com/ortizdavid/golang-fiber-webapp/entities" 7 | ) 8 | 9 | type TaskComplexityModel struct { 10 | } 11 | 12 | func (TaskComplexityModel) Create(complexity entities.TaskComplexity) (*gorm.DB, error) { 13 | db, _ := config.ConnectDB() 14 | defer config.DisconnectDB(db) 15 | result := db.Create(&complexity) 16 | if result.Error != nil { 17 | return nil, result.Error 18 | } 19 | return result, nil 20 | } 21 | 22 | func (TaskComplexityModel) FindAll() ([]entities.TaskComplexity, error) { 23 | db, _ := config.ConnectDB() 24 | defer config.DisconnectDB(db) 25 | var complexities []entities.TaskComplexity 26 | result := db.Find(&complexities) 27 | if result.Error != nil { 28 | return nil, result.Error 29 | } 30 | return complexities, nil 31 | } 32 | 33 | func (TaskComplexityModel) Update(complexity entities.TaskComplexity) (*gorm.DB, error) { 34 | db, _ := config.ConnectDB() 35 | defer config.DisconnectDB(db) 36 | result := db.Save(&complexity) 37 | if result.Error != nil { 38 | return nil, result.Error 39 | } 40 | return result, nil 41 | } 42 | 43 | func (TaskComplexityModel) FindById(id int) (entities.TaskComplexity, error) { 44 | db, _ := config.ConnectDB() 45 | defer config.DisconnectDB(db) 46 | var complexity entities.TaskComplexity 47 | result := db.First(&complexity, id) 48 | if result.Error != nil { 49 | return entities.TaskComplexity{}, result.Error 50 | } 51 | return complexity, nil 52 | } 53 | 54 | func (TaskComplexityModel) FindByCode(code string) (entities.TaskComplexity, error) { 55 | db, _ := config.ConnectDB() 56 | defer config.DisconnectDB(db) 57 | var complexity entities.TaskComplexity 58 | result := db.Where("code=?", code).First(&complexity) 59 | if result.Error != nil { 60 | return entities.TaskComplexity{}, result.Error 61 | } 62 | return complexity, nil 63 | } 64 | 65 | func (TaskComplexityModel) Count() (int64, error) { 66 | db, _ := config.ConnectDB() 67 | defer config.DisconnectDB(db) 68 | var count int64 69 | result := db.Table("task_complexity").Count(&count) 70 | if result.Error != nil { 71 | return 0, result.Error 72 | } 73 | return count, nil 74 | } 75 | -------------------------------------------------------------------------------- /models/task_model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "github.com/ortizdavid/golang-fiber-webapp/config" 6 | "github.com/ortizdavid/golang-fiber-webapp/entities" 7 | ) 8 | 9 | type TaskModel struct { 10 | LastInsertId int 11 | } 12 | 13 | func (taskModel *TaskModel) Create(task entities.Task) (*gorm.DB, error) { 14 | db, _ := config.ConnectDB() 15 | defer config.DisconnectDB(db) 16 | result := db.Create(&task) 17 | if result.Error != nil { 18 | return nil, result.Error 19 | } 20 | taskModel.LastInsertId = task.TaskId 21 | return result, nil 22 | } 23 | 24 | func (taskModel *TaskModel) CreateBatch(tasks []entities.Task) (*gorm.DB, error) { 25 | db, _ := config.ConnectDB() 26 | defer config.DisconnectDB(db) 27 | result := db.Create(&tasks) 28 | if result.Error != nil { 29 | return nil, result.Error 30 | } 31 | return result, nil 32 | } 33 | 34 | func (TaskModel) FindAll() ([]entities.Task, error) { 35 | db, _ := config.ConnectDB() 36 | defer config.DisconnectDB(db) 37 | var tasks []entities.Task 38 | result := db.Find(&tasks) 39 | if result.Error != nil { 40 | return nil, result.Error 41 | } 42 | return tasks, nil 43 | } 44 | 45 | func (TaskModel) Update(task entities.Task) (*gorm.DB, error) { 46 | db, _ := config.ConnectDB() 47 | defer config.DisconnectDB(db) 48 | result := db.Save(&task) 49 | if result.Error != nil { 50 | return nil, result.Error 51 | } 52 | return result, nil 53 | } 54 | 55 | func (TaskModel) FindById(id int) (entities.Task, error) { 56 | db, _ := config.ConnectDB() 57 | defer config.DisconnectDB(db) 58 | var task entities.Task 59 | result := db.First(&task, id) 60 | if result.Error != nil { 61 | return entities.Task{}, result.Error 62 | } 63 | return task, nil 64 | } 65 | 66 | func (TaskModel) FindByUniqueId(uniqueId string) (entities.Task, error) { 67 | db, _ := config.ConnectDB() 68 | defer config.DisconnectDB(db) 69 | var task entities.Task 70 | result := db.First(&task, "unique_id=?", uniqueId) 71 | if result.Error != nil { 72 | return entities.Task{}, result.Error 73 | } 74 | return task, nil 75 | } 76 | 77 | func (TaskModel) FindUserId(userId int) (entities.Task, error) { 78 | db, _ := config.ConnectDB() 79 | defer config.DisconnectDB(db) 80 | var task entities.Task 81 | result := db.First(&task, "user_id=?", userId) 82 | if result.Error != nil { 83 | return entities.Task{}, result.Error 84 | } 85 | return task, nil 86 | } 87 | 88 | func (TaskModel) Search(param string) ([]entities.TaskData, error) { 89 | db, _ := config.ConnectDB() 90 | defer config.DisconnectDB(db) 91 | var tasks []entities.TaskData 92 | result := db.Raw("SELECT * FROM view_task_data WHERE task_name=?", param).Scan(&tasks) 93 | if result.Error != nil { 94 | return nil, result.Error 95 | } 96 | return tasks, nil 97 | } 98 | 99 | func (TaskModel) GetDataById(id int) (entities.TaskData, error) { 100 | db, _ := config.ConnectDB() 101 | defer config.DisconnectDB(db) 102 | var taskData entities.TaskData 103 | result := db.Raw("SELECT * FROM view_task_data WHERE task_id=?", id).Scan(&taskData) 104 | if result.Error != nil { 105 | return entities.TaskData{}, result.Error 106 | } 107 | return taskData, nil 108 | } 109 | 110 | func (TaskModel) GetDataByUniqueId(uniqueId string) (entities.TaskData, error) { 111 | db, _ := config.ConnectDB() 112 | defer config.DisconnectDB(db) 113 | var taskData entities.TaskData 114 | result := db.Raw("SELECT * FROM view_task_data WHERE unique_id=?", uniqueId).Scan(&taskData) 115 | if result.Error != nil { 116 | return entities.TaskData{}, result.Error 117 | } 118 | return taskData, nil 119 | } 120 | 121 | func (TaskModel) FindAllData() ([]entities.TaskData, error) { 122 | db, _ := config.ConnectDB() 123 | defer config.DisconnectDB(db) 124 | var tasks []entities.TaskData 125 | result := db.Raw("SELECT * FROM view_task_data").Scan(&tasks) 126 | if result.Error != nil { 127 | return nil, result.Error 128 | } 129 | return tasks, nil 130 | } 131 | 132 | 133 | func (TaskModel) FindAllDataLimit(start int, end int) ([]entities.TaskData, error) { 134 | db, _ := config.ConnectDB() 135 | defer config.DisconnectDB(db) 136 | var tasks []entities.TaskData 137 | result := db.Raw("SELECT * FROM view_task_data LIMIT ?, ?", start, end).Scan(&tasks) 138 | if result.Error != nil { 139 | return nil, result.Error 140 | } 141 | return tasks, nil 142 | } 143 | 144 | func (TaskModel) FindAllDataByTaskId(taskId int) ([]entities.TaskData, error) { 145 | db, _ := config.ConnectDB() 146 | defer config.DisconnectDB(db) 147 | var tasks []entities.TaskData 148 | result := db.Raw("SELECT * FROM view_task_data WHERE task_id=?", taskId).Scan(&tasks) 149 | if result.Error != nil { 150 | return nil, result.Error 151 | } 152 | return tasks, nil 153 | } 154 | 155 | func (TaskModel) FindAllDataByStatus(status string) ([]entities.TaskData, error) { 156 | db, _ := config.ConnectDB() 157 | defer config.DisconnectDB(db) 158 | var tasks []entities.TaskData 159 | result := db.Raw("SELECT * FROM view_task_data WHERE status_name=?", status).Scan(&tasks) 160 | if result.Error != nil { 161 | return nil, result.Error 162 | } 163 | return tasks, nil 164 | } 165 | 166 | func (TaskModel) FindAllDataByStatusLimit(status string, start int, end int) ([]entities.TaskData, error) { 167 | db, _ := config.ConnectDB() 168 | defer config.DisconnectDB(db) 169 | var tasks []entities.TaskData 170 | result := db.Raw("SELECT * FROM view_task_data WHERE status_name=? LIMIT ?, ?", status, start, end).Scan(&tasks) 171 | if result.Error != nil { 172 | return nil, result.Error 173 | } 174 | return tasks, nil 175 | } 176 | 177 | func (TaskModel) FindAllDataByUserId(userId int) ([]entities.TaskData, error) { 178 | db, _ := config.ConnectDB() 179 | defer config.DisconnectDB(db) 180 | var tasks []entities.TaskData 181 | result := db.Raw("SELECT * FROM view_task_data WHERE user_id=?", userId).Scan(&tasks) 182 | if result.Error != nil { 183 | return nil, result.Error 184 | } 185 | return tasks, nil 186 | } 187 | 188 | func (TaskModel) FindAllDataByUserIdLimit(userId int, start int, end int) ([]entities.TaskData, error) { 189 | db, _ := config.ConnectDB() 190 | defer config.DisconnectDB(db) 191 | var tasks []entities.TaskData 192 | result := db.Raw("SELECT * FROM view_task_data WHERE user_id=? LIMIT ?, ?", userId, start, end).Scan(&tasks) 193 | if result.Error != nil { 194 | return nil, result.Error 195 | } 196 | return tasks, nil 197 | } 198 | 199 | func (TaskModel) Count() (int64, error) { 200 | db, _ := config.ConnectDB() 201 | defer config.DisconnectDB(db) 202 | var count int64 203 | result := db.Table("tasks").Count(&count) 204 | if result.Error != nil { 205 | return 0, result.Error 206 | } 207 | return count, nil 208 | } 209 | 210 | func (TaskModel) CountByStatus(status string) (int64, error) { 211 | db, _ := config.ConnectDB() 212 | defer config.DisconnectDB(db) 213 | var count int64 214 | result := db.Table("view_task_data").Where("status_code=?", status).Count(&count) 215 | if result.Error != nil { 216 | return 0, result.Error 217 | } 218 | return count, nil 219 | } 220 | 221 | func (TaskModel) CountByUser(userId int) (int64, error) { 222 | db, _ := config.ConnectDB() 223 | defer config.DisconnectDB(db) 224 | var count int64 225 | result := db.Table("tasks").Where("user_id=?", userId).Count(&count) 226 | if result.Error != nil { 227 | return 0, result.Error 228 | } 229 | return count, nil 230 | } 231 | 232 | func (TaskModel) CountByStatusAndUser(status string, userId int) (int64, error) { 233 | db, _ := config.ConnectDB() 234 | defer config.DisconnectDB(db) 235 | var count int64 236 | result := db.Table("view_task_data").Where("status_code=? AND user_id=?", status, userId).Count(&count) 237 | if result.Error != nil { 238 | return 0, result.Error 239 | } 240 | return count, nil 241 | } 242 | -------------------------------------------------------------------------------- /models/task_status_model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "github.com/ortizdavid/golang-fiber-webapp/config" 6 | "github.com/ortizdavid/golang-fiber-webapp/entities" 7 | ) 8 | 9 | type TaskStatusModel struct { 10 | } 11 | 12 | func (TaskStatusModel) Create(status entities.TaskStatus) (*gorm.DB, error) { 13 | db, _ := config.ConnectDB() 14 | defer config.DisconnectDB(db) 15 | result := db.Create(&status) 16 | if result.Error != nil { 17 | return nil, result.Error 18 | } 19 | return result, nil 20 | } 21 | 22 | func (TaskStatusModel) FindAll() ([]entities.TaskStatus, error) { 23 | db, _ := config.ConnectDB() 24 | defer config.DisconnectDB(db) 25 | statuses := []entities.TaskStatus{} 26 | result := db.Find(&statuses) 27 | if result.Error != nil { 28 | return nil, result.Error 29 | } 30 | return statuses, nil 31 | } 32 | 33 | func (TaskStatusModel) Update(status entities.TaskStatus) (*gorm.DB, error) { 34 | db, _ := config.ConnectDB() 35 | defer config.DisconnectDB(db) 36 | result := db.Save(&status) 37 | if result.Error != nil { 38 | return nil, result.Error 39 | } 40 | return result, nil 41 | } 42 | 43 | func (TaskStatusModel) FindById(id int) (entities.TaskStatus, error) { 44 | db, _ := config.ConnectDB() 45 | defer config.DisconnectDB(db) 46 | var status entities.TaskStatus 47 | result := db.First(&status, id) 48 | if result.Error != nil { 49 | return entities.TaskStatus{}, result.Error 50 | } 51 | return status, nil 52 | } 53 | 54 | func (TaskStatusModel) FindByCode(code string) (entities.TaskStatus, error) { 55 | db, _ := config.ConnectDB() 56 | defer config.DisconnectDB(db) 57 | var status entities.TaskStatus 58 | result := db.Where("code=?", code).First(&status) 59 | if result.Error != nil { 60 | return entities.TaskStatus{}, result.Error 61 | } 62 | return status, nil 63 | } 64 | 65 | func (TaskStatusModel) Count() (int64, error) { 66 | db, _ := config.ConnectDB() 67 | defer config.DisconnectDB(db) 68 | var count int64 69 | result := db.Table("task_status").Count(&count) 70 | if result.Error != nil { 71 | return 0, result.Error 72 | } 73 | return count, nil 74 | } 75 | -------------------------------------------------------------------------------- /models/user_model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "github.com/ortizdavid/golang-fiber-webapp/config" 6 | "github.com/ortizdavid/golang-fiber-webapp/entities" 7 | ) 8 | 9 | type UserModel struct { 10 | LastInsertId int 11 | } 12 | 13 | func (userModel *UserModel) Create(user entities.User) (*gorm.DB, error) { 14 | db, _ := config.ConnectDB() 15 | defer config.DisconnectDB(db) 16 | result := db.Create(&user) 17 | if result.Error != nil { 18 | return nil, result.Error 19 | } 20 | userModel.LastInsertId = user.UserId 21 | return result, nil 22 | } 23 | 24 | func (UserModel) FindAll() ([]entities.User, error) { 25 | db, _ := config.ConnectDB() 26 | defer config.DisconnectDB(db) 27 | var users []entities.User 28 | result := db.Find(&users) 29 | if result.Error != nil { 30 | return nil, result.Error 31 | } 32 | return users, nil 33 | } 34 | 35 | func (UserModel) Update(user entities.User) (*gorm.DB, error) { 36 | db, _ := config.ConnectDB() 37 | defer config.DisconnectDB(db) 38 | result := db.Save(&user) 39 | if result.Error != nil { 40 | return nil, result.Error 41 | } 42 | return result, nil 43 | } 44 | 45 | func (UserModel) FindById(id int) (entities.User, error) { 46 | db, _ := config.ConnectDB() 47 | defer config.DisconnectDB(db) 48 | var user entities.User 49 | result := db.First(&user, id) 50 | if result.Error != nil { 51 | return entities.User{}, result.Error 52 | } 53 | return user, nil 54 | } 55 | 56 | func (UserModel) FindByUniqueId(uniqueId string) (entities.User, error) { 57 | db, _ := config.ConnectDB() 58 | defer config.DisconnectDB(db) 59 | var user entities.User 60 | result := db.First(&user, "unique_id=?", uniqueId) 61 | if result.Error != nil { 62 | return entities.User{}, result.Error 63 | } 64 | return user, nil 65 | } 66 | 67 | func (UserModel) FindByUserName(userName string) (entities.User, error) { 68 | db, _ := config.ConnectDB() 69 | defer config.DisconnectDB(db) 70 | var user entities.User 71 | result := db.First(&user, "user_name=?", userName) 72 | if result.Error != nil { 73 | return entities.User{}, result.Error 74 | } 75 | return user, nil 76 | } 77 | 78 | func (UserModel) FindByToken(token string) (entities.User, error) { 79 | db, _ := config.ConnectDB() 80 | defer config.DisconnectDB(db) 81 | var user entities.User 82 | result := db.First(&user, "token=?", token) 83 | if result.Error != nil { 84 | return entities.User{}, result.Error 85 | } 86 | return user, nil 87 | } 88 | 89 | func (UserModel) Search(param interface{}) ([]entities.UserData, error) { 90 | db, _ := config.ConnectDB() 91 | defer config.DisconnectDB(db) 92 | var users []entities.UserData 93 | result := db.Raw("SELECT * FROM view_user_data WHERE user_name=?", param).Scan(&users) 94 | if result.Error != nil { 95 | return nil, result.Error 96 | } 97 | return users, nil 98 | } 99 | 100 | func (UserModel) Count() (int64, error) { 101 | db, _ := config.ConnectDB() 102 | defer config.DisconnectDB(db) 103 | var count int64 104 | result := db.Table("users").Count(&count) 105 | if result.Error != nil { 106 | return 0, result.Error 107 | } 108 | return count, nil 109 | } 110 | 111 | func (UserModel) FindAllOrdered() ([]entities.User, error) { 112 | db, _ := config.ConnectDB() 113 | defer config.DisconnectDB(db) 114 | var users []entities.User 115 | result := db.Order("user_name ASC").Find(&users) 116 | if result.Error != nil { 117 | return nil, result.Error 118 | } 119 | return users, nil 120 | } 121 | 122 | func (UserModel) GetDataById(id int) (entities.UserData, error) { 123 | db, _ := config.ConnectDB() 124 | defer config.DisconnectDB(db) 125 | var userData entities.UserData 126 | result := db.Raw("SELECT * FROM view_user_data WHERE user_id=?", id).Scan(&userData) 127 | if result.Error != nil { 128 | return entities.UserData{}, result.Error 129 | } 130 | return userData, nil 131 | } 132 | 133 | func (UserModel) GetDataByUniqueId(uniqueId string) (entities.UserData, error) { 134 | db, _ := config.ConnectDB() 135 | defer config.DisconnectDB(db) 136 | var userData entities.UserData 137 | result := db.Raw("SELECT * FROM view_user_data WHERE unique_id=?", uniqueId).Scan(&userData) 138 | if result.Error != nil { 139 | return entities.UserData{}, result.Error 140 | } 141 | return userData, nil 142 | } 143 | 144 | func (UserModel) FindAllData() ([]entities.UserData, error) { 145 | db, _ := config.ConnectDB() 146 | defer config.DisconnectDB(db) 147 | var users []entities.UserData 148 | result := db.Raw("SELECT * FROM view_user_data").Scan(&users) 149 | if result.Error != nil { 150 | return nil, result.Error 151 | } 152 | return users, nil 153 | } 154 | 155 | func (UserModel) FindAllDataLimit(start int, end int) ([]entities.UserData, error) { 156 | db, _ := config.ConnectDB() 157 | defer config.DisconnectDB(db) 158 | var users []entities.UserData 159 | result := db.Raw("SELECT * FROM view_user_data LIMIT ?, ?", start, end).Scan(&users) 160 | if result.Error != nil { 161 | return nil, result.Error 162 | } 163 | return users, nil 164 | } 165 | 166 | func (UserModel) Exists(userName string, password string) (bool, error) { 167 | db, _ := config.ConnectDB() 168 | defer config.DisconnectDB(db) 169 | var user entities.User 170 | result := db.Where("user_name=? AND password=?", userName, password).Find(&user) 171 | if result.Error != nil { 172 | return false, result.Error 173 | } 174 | return user.UserId != 0, nil 175 | } 176 | 177 | func (UserModel) ExistsActive(userName string, password string) (bool, error) { 178 | db, _ := config.ConnectDB() 179 | defer config.DisconnectDB(db) 180 | var user entities.User 181 | result := db.Where("user_name=? AND password=? AND active='Yes'", userName, password).Find(&user) 182 | if result.Error != nil { 183 | return false, result.Error 184 | } 185 | return user.UserId != 0, nil 186 | } 187 | 188 | func (UserModel) ExistsActiveUser(userName string) (bool, error) { 189 | db, _ := config.ConnectDB() 190 | defer config.DisconnectDB(db) 191 | var user entities.User 192 | result := db.Where("user_name=? AND active='Yes'", userName).Find(&user) 193 | if result.Error != nil { 194 | return false, result.Error 195 | } 196 | return user.UserId != 0, nil 197 | } 198 | 199 | func (UserModel) GetByUserNameAndPassword(userName string, password string) (entities.UserData, error) { 200 | db, _ := config.ConnectDB() 201 | defer config.DisconnectDB(db) 202 | var userData entities.UserData 203 | result := db.Raw("SELECT * FROM view_user_data WHERE user_name=? AND password=?", userName, password).Scan(&userData) 204 | if result.Error != nil { 205 | return entities.UserData{}, result.Error 206 | } 207 | return userData, nil 208 | } 209 | 210 | func (UserModel) GetByUserName(userName string) (entities.UserData, error) { 211 | db, _ := config.ConnectDB() 212 | defer config.DisconnectDB(db) 213 | var userData entities.UserData 214 | result := db.Raw("SELECT * FROM view_user_data WHERE user_name=?", userName).Scan(&userData) 215 | if result.Error != nil { 216 | return entities.UserData{}, result.Error 217 | } 218 | return userData, nil 219 | } 220 | 221 | func (UserModel) FindAllByStatus(statusName string) ([]entities.UserData, error) { 222 | db, _ := config.ConnectDB() 223 | defer config.DisconnectDB(db) 224 | users := []entities.UserData{} 225 | result := db.Raw("SELECT * FROM view_user_data WHERE active=?", statusName).Find(&users) 226 | if result.Error != nil { 227 | return nil, result.Error 228 | } 229 | return users, nil 230 | } 231 | 232 | func (UserModel) FindAllByRole(roleName string) ([]entities.UserData, error) { 233 | db, _ := config.ConnectDB() 234 | defer config.DisconnectDB(db) 235 | var users []entities.UserData 236 | result := db.Raw("SELECT * FROM view_user_data WHERE role_name=?", roleName).Find(&users) 237 | if result.Error != nil { 238 | return nil, result.Error 239 | } 240 | return users, nil 241 | } 242 | 243 | func (UserModel) FindInactiveByRole(roleName string) ([]entities.UserData, error) { 244 | db, _ := config.ConnectDB() 245 | defer config.DisconnectDB(db) 246 | users := []entities.UserData{} 247 | result := db.Raw("SELECT * FROM view_user_data WHERE role_name=? and active='No'", roleName).Find(&users) 248 | if result.Error != nil { 249 | return nil, result.Error 250 | } 251 | return users, nil 252 | } 253 | -------------------------------------------------------------------------------- /setup/create_user.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "log/slog" 7 | "github.com/ortizdavid/golang-fiber-webapp/entities" 8 | "github.com/ortizdavid/golang-fiber-webapp/helpers" 9 | "github.com/ortizdavid/golang-fiber-webapp/models" 10 | ) 11 | 12 | func main() { 13 | var userName, password, roleName string 14 | var roleId int 15 | var roleModel models.RoleModel 16 | var userModel models.UserModel 17 | 18 | fmt.Println("Create Users") 19 | fmt.Print("Role: \n\t[1]-Admin\n\t[2]-Normal: ") 20 | fmt.Scanln(&roleId) 21 | fmt.Print("Username: ") 22 | fmt.Scanln(&userName) 23 | fmt.Print("Password: ") 24 | fmt.Scanln(&password) 25 | 26 | if roleId < 1 || roleId > 2 { 27 | roleId = 1 28 | } 29 | role, _ := roleModel.FindById(roleId) 30 | roleName = role.RoleName 31 | 32 | user := entities.User{ 33 | UserId: 0, 34 | RoleId: roleId, 35 | UserName: userName, 36 | Password: helpers.HashPassword(password), 37 | Active: "Yes", 38 | Image: "", 39 | UniqueId: helpers.GenerateUUID(), 40 | CreatedAt: time.Now(), 41 | UpdatedAt: time.Now(), 42 | } 43 | userModel.Create(user) 44 | slog.Info("User '%s' Created with role '%s'", userName, roleName) 45 | } -------------------------------------------------------------------------------- /static/css/signin.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | body { 7 | display: flex; 8 | align-items: center; 9 | padding-top: 40px; 10 | padding-bottom: 40px; 11 | background-color: #f5f5f5; 12 | } 13 | 14 | .form-signin { 15 | max-width: 330px; 16 | padding: 15px; 17 | } 18 | 19 | .form-signin .form-floating:focus-within { 20 | z-index: 2; 21 | } 22 | 23 | .form-signin input[type="email"] { 24 | margin-bottom: -1px; 25 | border-bottom-right-radius: 0; 26 | border-bottom-left-radius: 0; 27 | } 28 | 29 | .form-signin input[type="password"] { 30 | margin-bottom: 10px; 31 | border-top-left-radius: 0; 32 | border-top-right-radius: 0; 33 | } 34 | -------------------------------------------------------------------------------- /static/images/blank-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/static/images/blank-user.png -------------------------------------------------------------------------------- /static/images/golang-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/static/images/golang-logo.png -------------------------------------------------------------------------------- /static/uploads/imgs/1695205811266044400.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/static/uploads/imgs/1695205811266044400.jpg -------------------------------------------------------------------------------- /static/uploads/imgs/1695807195804061300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/static/uploads/imgs/1695807195804061300.png -------------------------------------------------------------------------------- /static/uploads/imgs/1695810331709409800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/static/uploads/imgs/1695810331709409800.png -------------------------------------------------------------------------------- /templates/auth/get-recovery-link.html: -------------------------------------------------------------------------------- 1 | {{ template "partials/header-auth" . }} 2 | 3 |
4 |
5 | 6 | 7 |
8 |
9 | 13 |
14 | Registerd? Login here 15 |
16 |

© 2023

17 |
18 | 19 | {{ template "partials/footer-auth" . }} -------------------------------------------------------------------------------- /templates/auth/login.html: -------------------------------------------------------------------------------- 1 | {{ template "partials/header-auth" . }} 2 | 3 |
4 |
5 | 6 | 7 |
8 |
9 | 10 | 11 |
12 | 13 |
14 | Forgot password? click Here 15 |
16 |

© 2023

17 |
18 | 19 | {{ template "partials/footer-auth" . }} -------------------------------------------------------------------------------- /templates/auth/recover-password.html: -------------------------------------------------------------------------------- 1 | {{ template "partials/header-auth" . }} 2 | 3 |
4 |
5 | 6 | 7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 | 15 |
16 |
17 | 21 |
22 | Registered? Login here 23 |
24 |

© 2023

25 |
26 | 27 | {{ template "partials/footer-auth" . }} -------------------------------------------------------------------------------- /templates/auth/recovery-link.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ortizdavid/golang-fiber-webapp/3c030615979fcc9ad724c2630c60e1aed5cebd9b/templates/auth/recovery-link.html -------------------------------------------------------------------------------- /templates/back-office/404-home.html: -------------------------------------------------------------------------------- 1 |

HOME Not Found

-------------------------------------------------------------------------------- /templates/back-office/home-admin.html: -------------------------------------------------------------------------------- 1 |

2 |
3 |

Users

4 |
5 |
{{ .Statistics.Users }}
6 |
7 |
8 | 9 |
10 |

Tasks

11 |
12 |
{{ .Statistics.Tasks }}
13 |
14 |
15 | 16 |
17 |

Completed Tasks

18 |
19 |
{{ .Statistics.CompletedTasks }}
20 |
21 |
22 | 23 |
24 |

In Progress Tasks

25 |
26 |
{{ .Statistics.InProgressTasks }}
27 |
28 |
29 | 30 |
31 |

Pending Tasks

32 |
33 |
{{ .Statistics.PendingTasks }}
34 |
35 |
36 | 37 |
38 |

Cancelled Tasks

39 |
40 |
{{ .Statistics.CanceledTasks }}
41 |
42 |
43 | 44 |
45 |

Blocked Tasks

46 |
47 |
{{ .Statistics.BlockedTasks }}
48 |
49 |
50 | -------------------------------------------------------------------------------- /templates/back-office/home-normal.html: -------------------------------------------------------------------------------- 1 |

2 |
3 |

My Tasks

4 |
5 |
{{ .Statistics.Tasks }}
6 |
7 |
-------------------------------------------------------------------------------- /templates/back-office/home.html: -------------------------------------------------------------------------------- 1 | {{ template "partials/header-back" . }} 2 | 3 |
4 | {{ if eq .LoggedUser.RoleCode "admin" }} 5 | {{ template "back-office/home-admin". }} 6 | {{ else if eq .LoggedUser.RoleCode "normal" }} 7 | {{ template "back-office/home-normal". }} 8 | {{ else }} 9 | {{ template "back-office/404-home". }} 10 | {{ end }} 11 |
12 | 13 | {{ template "partials/footer-back" . }} -------------------------------------------------------------------------------- /templates/back-office/user-data.html: -------------------------------------------------------------------------------- 1 | {{ template "partials/header-back" . }} 2 | 3 |
4 | {{ if eq .LoggedUser.Image ""}} 5 |
6 | 7 | Update Image 8 | 9 | {{ end }} 10 | 11 |

Userame: {{ .LoggedUser.UserName }}

12 |

Role: {{ .LoggedUser.RoleName }}

13 |

Active: {{ .LoggedUser.Active }}

14 |

Created at: {{ .LoggedUser.CreatedAtStr }}

15 |

Updated at: {{ .LoggedUser.UpdatedAtStr }}

16 | 17 |
18 | 19 | {{ template "partials/footer-back" . }} -------------------------------------------------------------------------------- /templates/error/authentication.html: -------------------------------------------------------------------------------- 1 | {{ template "partials/header-auth" . }} 2 | 3 |
4 | Authentication failed. Please Login Here 5 |
6 | 7 | {{ template "partials/footer-auth" . }} -------------------------------------------------------------------------------- /templates/error/pagination.html: -------------------------------------------------------------------------------- 1 | {{ template "partials/header-back" . }} 2 | 3 |
4 |

No Records Found! has {{ .TotalPages }} pages of results

5 |
6 | 7 | {{ template "partials/footer-back" . }} -------------------------------------------------------------------------------- /templates/front-office/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ .Title }} 8 | 9 | 10 | 11 |
12 |
13 |

{{ .Title }}

14 |

Exmple sugest an simple and functional Structure for Golang Web Apps

15 |
16 | 17 | -------------------------------------------------------------------------------- /templates/front-office/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ .Title }} 8 | 9 | 10 | 11 |
12 |
13 |

{{ .Title }}

14 |

Golang Web App - Fiber Framework

15 | 16 |

17 | 18 |

19 |
20 | 21 | -------------------------------------------------------------------------------- /templates/partials/404-menu.html: -------------------------------------------------------------------------------- 1 |

Menu Not Found

-------------------------------------------------------------------------------- /templates/partials/footer-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /templates/partials/footer-back.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /templates/partials/header-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ .Title }} 8 | 9 | 10 | 55 | 56 | 57 | 58 |
59 |
60 | 61 |
62 |

63 | 64 |

{{ .Title }}

65 |