diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000000000000000000000000000000000000..4d1ded0118d91ed44f2dc7c7bb680e7327975fa0 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,27 @@ +version: '3.8' + +services: + goservice: + image: graduit-be + env_file: .env + ports: + - "8080:8080" + + nginx: + build: ./nginx + ports: + - "80:80" + - "443:443" + depends_on: + - goservice + volumes: + - ./data/certbot/conf:/etc/letsencrypt + - ./data/certbot/www:/var/www/certbot + command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" + + certbot: + image: certbot/certbot + volumes: + - ./data/certbot/conf:/etc/letsencrypt + - ./data/certbot/www:/var/www/certbot + entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index e4d8efbada9c8fb11ab4fda0d6550af6e4b95107..3760b0c8d2a89a496a5228a15e3a8fde78465ebd 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,19 +1,5 @@ version: "3.8" services: - graduit_server: - build: . - ports: - - 8080:8080 - hostname: graduit_server - env_file: .env - volumes: - - ./:/usr/app - - /usr/app/db-data - restart: always - depends_on: - - graduit_db - - s2_db - s2_db: image: postgres:alpine ports: diff --git a/init-letsencrypt.sh b/init-letsencrypt.sh new file mode 100755 index 0000000000000000000000000000000000000000..6f0c741f9e8425af42f3d30418843e3ec5a70fa8 --- /dev/null +++ b/init-letsencrypt.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +if ! [ -x "$(command -v docker-compose)" ]; then + echo 'Error: docker-compose is not installed.' >&2 + exit 1 +fi + +domains=(s1api.domainbuattesting.my.id) +rsa_key_size=4096 +data_path="./data/certbot" +email="" # Adding a valid address is strongly recommended +staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits + +if [ -d "$data_path" ]; then + read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision + if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then + exit + fi +fi + + +if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then + echo "### Downloading recommended TLS parameters ..." + mkdir -p "$data_path/conf" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem" + echo +fi + +echo "### Creating dummy certificate for $domains ..." +path="/etc/letsencrypt/live/$domains" +mkdir -p "$data_path/conf/live/$domains" +docker-compose -f docker-compose.prod.yml run --rm --entrypoint "\ + openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\ + -keyout '$path/privkey.pem' \ + -out '$path/fullchain.pem' \ + -subj '/CN=localhost'" certbot +echo + + +echo "### Starting nginx ..." +docker-compose -f docker-compose.prod.yml up --force-recreate -d nginx +echo + +echo "### Deleting dummy certificate for $domains ..." +docker-compose -f docker-compose.prod.yml run --rm --entrypoint "\ + rm -Rf /etc/letsencrypt/live/$domains && \ + rm -Rf /etc/letsencrypt/archive/$domains && \ + rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot +echo + + +echo "### Requesting Let's Encrypt certificate for $domains ..." +#Join $domains to -d args +domain_args="" +for domain in "${domains[@]}"; do + domain_args="$domain_args -d $domain" +done + +# Select appropriate email arg +case "$email" in + "") email_arg="--register-unsafely-without-email" ;; + *) email_arg="--email $email" ;; +esac + +# Enable staging mode if needed +if [ $staging != "0" ]; then staging_arg="--staging"; fi + +docker-compose -f docker-compose.prod.yml run --rm --entrypoint "\ + certbot certonly --webroot -w /var/www/certbot \ + $staging_arg \ + $email_arg \ + $domain_args \ + --rsa-key-size $rsa_key_size \ + --agree-tos \ + --force-renewal" certbot +echo + +echo "### Reloading nginx ..." +docker-compose -f docker-compose.prod.yml exec nginx nginx -s reload diff --git a/src/handler/handler.go b/src/handler/handler.go index d9f5c953353ca9d8806914d9b8b1341f08017a6e..19ada8be1289f6ff9d5ad714031abb6a903bb784 100644 --- a/src/handler/handler.go +++ b/src/handler/handler.go @@ -14,6 +14,8 @@ import ( dashboardMahasiswaTransport "gitlab.informatika.org/k-01-11/graduit-be/src/module/dashboard_mahasiswa/transport" dashboardTimTugasConfig "gitlab.informatika.org/k-01-11/graduit-be/src/module/dashboard_tim_tugas/config" dashboardTimTugasTransport "gitlab.informatika.org/k-01-11/graduit-be/src/module/dashboard_tim_tugas/transport" + dashboardTimTAConfig "gitlab.informatika.org/k-01-11/graduit-be/src/module/dashboard_tim_ta/config" + dashboardTimTATransport "gitlab.informatika.org/k-01-11/graduit-be/src/module/dashboard_tim_ta/transport" pendaftaranConfig "gitlab.informatika.org/k-01-11/graduit-be/src/module/pendaftaran/config" pendaftaranTransport "gitlab.informatika.org/k-01-11/graduit-be/src/module/pendaftaran/transport" pendaftaranSidSemConfig "gitlab.informatika.org/k-01-11/graduit-be/src/module/pendaftaran_sidsem/config" @@ -49,6 +51,8 @@ type Service struct { TIMTASchedulingHandler *schedulingTransport.TIMTASchedulingHandler MahasiswaPendaftaranSidSemHandler *pendaftaranSidSemTransport.MahasiswaPendaftaranSidSemHandler TIMTAPendaftaranSidSemHandler *pendaftaranSidSemTransport.TIMTAPendaftaranSidSemHandler + TIMTADashboardTimHandler *dashboardTimTATransport.TIMTADashboardTimHandler + } func MakeHandler() *Service { @@ -118,6 +122,11 @@ func MakeHandler() *Service { DBRead: dbRead, }) + TIMTADashboardHandler := dashboardTimTATransport.NewTIMTADashboardTimHandler(dashboardTimTAConfig.DashboardTimTransportConfig{ + DBWrite: dbWrite, + DBRead: dbRead, + }) + TIMTASchedulingHandlerHandler := schedulingTransport.NewTIMTASchedulingHandler(schedulingConfig.SchedulingTransportConfig{ DBWrite: dbWrite, DBRead: dbRead, @@ -150,5 +159,6 @@ func MakeHandler() *Service { MahasiswaPendaftaranSidSemHandler: MahasiswaPendaftaranSidSemHandler, TIMTAPendaftaranSidSemHandler: TIMTAPendaftaranSidSemHandler, AdminRuanganHandler: AdminRuanganHandler, + TIMTADashboardTimHandler: TIMTADashboardHandler, } } diff --git a/src/handler/server.go b/src/handler/server.go index 0353fb9be7c539da2c75dc3d44b44c5e3bbc5be4..bbefc56b5425682207fe15c3bfb79f4301894352 100644 --- a/src/handler/server.go +++ b/src/handler/server.go @@ -63,6 +63,7 @@ func (s *Service) InitializeRoutes() *echo.Echo { TIMTAGroup.Use(middleware.Validator("S1_TIM_TA")) s.TIMTASchedulingHandler.MountTIMTA(TIMTAGroup) s.TIMTAPendaftaranSidSemHandler.MountTIMTA(TIMTAGroup) + s.TIMTADashboardTimHandler.MountTIMTA(TIMTAGroup) return e } diff --git a/src/module/dashboard_tim_ta/entity/dashboard_tim_ta.go b/src/module/dashboard_tim_ta/entity/dashboard_tim_ta.go index 1fe62afd879a141e6e04d9011fda4c9130548368..c38f71ae3fb147624d3b178541cb4603d163bb8d 100644 --- a/src/module/dashboard_tim_ta/entity/dashboard_tim_ta.go +++ b/src/module/dashboard_tim_ta/entity/dashboard_tim_ta.go @@ -12,3 +12,12 @@ type DataPendaftaran struct { DosenPembimbing string `json:"dosbing"` Status string `json:"status"` } + +type StatusMahasiswa struct { + Id string `json:"id"` + NIM string `json:"nim"` + Nama string `json:"nama"` + PengajuanTopik bool + SeminarProposal bool + Sidang bool +} diff --git a/src/module/dashboard_tim_ta/internal/repository/dashboard_tim_ta.go b/src/module/dashboard_tim_ta/internal/repository/dashboard_tim_ta.go index dd0e75df33a1053aa756ea9230f65182bfbdaeac..a17c999b0cefddc5becde43020c43c75f30d9ed3 100644 --- a/src/module/dashboard_tim_ta/internal/repository/dashboard_tim_ta.go +++ b/src/module/dashboard_tim_ta/internal/repository/dashboard_tim_ta.go @@ -70,3 +70,69 @@ func (repo *DashboardTimRepo) GetDataDashboard(offset, size int) (output []entit return dataMahasiswa, nil } + +func (repo *DashboardTimRepo) GetStatusDashboard(offset, size int) (output []entity.StatusMahasiswa, err error) { + var statusMahasiswa []entity.StatusMahasiswa + var mahasiswa []models.Pengguna + + // Find all users that have role S1_MAHASISWA + result := repo.DBRead.Where("roles @> ?", "{S1_MAHASISWA}").Limit(size).Offset(offset).Find(&mahasiswa) + if result.Error != nil { + return nil, result.Error + } + + for _, mhs := range mahasiswa { + var proposal models.PendaftaranTA + var sidsem models.PendaftaranSidSem + var taStatus, seminarStatus, sidangStatus bool + + // Check status for TA + taResult := repo.DBRead.Where("id_mahasiswa = ?", mhs.ID).First(&proposal) + if taResult.Error != nil { + taStatus = false + } else if proposal.Status != "APPROVED" { + taStatus = false + } else { + taStatus = true + } + + // Check status for Seminar Proposal + seminarResult := repo.DBRead.Where("pendaftaran_ta_id = ? AND tipe = ?", proposal.ID, "Seminar Proposal").First(&sidsem) + if seminarResult.Error != nil { + seminarStatus = false + } else if !sidsem.Ditolak { + seminarStatus = false + } else if sidsem.Ditolak { + seminarStatus = true + } else { + seminarStatus = false + } + + // Check status for Sidang Proposal + sidangResult := repo.DBRead.Where("pendaftaran_ta_id = ? AND tipe = ?", mhs.ID, "Sidang Proposal").First(&sidsem) + if sidangResult.Error != nil { + sidangStatus = false + } else if !sidsem.Ditolak { + sidangStatus = false + } else if sidsem.Ditolak { + sidangStatus = true + } else { + sidangStatus = false + } + + // Create StatusMahasiswa entity + data := entity.StatusMahasiswa{ + Id: mhs.ID, + NIM: mhs.NIM, + Nama: mhs.Nama, + PengajuanTopik: taStatus, + SeminarProposal: seminarStatus, + Sidang: sidangStatus, + } + + // Append to output + statusMahasiswa = append(statusMahasiswa, data) + } + + return statusMahasiswa, nil +} diff --git a/src/module/dashboard_tim_ta/internal/usecase/dashboard_tim_ta.go b/src/module/dashboard_tim_ta/internal/usecase/dashboard_tim_ta.go index d83a54a19b9670f4ec96ee93f47b0af69cd01d07..92c0e1aa7acaa03269646400f8fd6a52259dd1e3 100644 --- a/src/module/dashboard_tim_ta/internal/usecase/dashboard_tim_ta.go +++ b/src/module/dashboard_tim_ta/internal/usecase/dashboard_tim_ta.go @@ -4,6 +4,7 @@ import "gitlab.informatika.org/k-01-11/graduit-be/src/module/dashboard_tim_ta/en type DashboardTimUseCase interface { GetDataMahasiswa(offset, size int) (output []entity.DataPendaftaran, err error) + GetStatusMahasiswa(offset, size int) (output []entity.StatusMahasiswa, err error) } type DashboardTimUc struct { @@ -25,3 +26,13 @@ func (uc *DashboardTimUc) GetDataMahasiswa(offset, size int) (output []entity.Da return listMahasiswa, nil } + +func (uc *DashboardTimUc) GetStatusMahasiswa(offset, size int) (output []entity.StatusMahasiswa, err error) { + listMahasiswa, err := uc.dashboardTimRepo.GetStatusDashboard(offset, size) + + if err != nil { + return listMahasiswa, err + } + + return listMahasiswa, nil +} diff --git a/src/module/dashboard_tim_ta/internal/usecase/repository.go b/src/module/dashboard_tim_ta/internal/usecase/repository.go index 9114e4d92a09b8402175e7aa77de7a570fd29fb5..559e2203aed7c45b9af35c107381c25b1829f98e 100644 --- a/src/module/dashboard_tim_ta/internal/usecase/repository.go +++ b/src/module/dashboard_tim_ta/internal/usecase/repository.go @@ -4,4 +4,5 @@ import "gitlab.informatika.org/k-01-11/graduit-be/src/module/dashboard_tim_ta/en type DashboardTimRepository interface { GetDataDashboard(offset, size int) (output []entity.DataPendaftaran, err error) + GetStatusDashboard(offset, size int) (output []entity.StatusMahasiswa, err error) } diff --git a/src/module/dashboard_tim_ta/transport/admin_handler.go b/src/module/dashboard_tim_ta/transport/timta_handler.go similarity index 63% rename from src/module/dashboard_tim_ta/transport/admin_handler.go rename to src/module/dashboard_tim_ta/transport/timta_handler.go index 1d5f73545c5b24299ebb3b6a95d5b67f811e0e40..cf6983ae87e31e48717eaeac696367c244147673 100644 --- a/src/module/dashboard_tim_ta/transport/admin_handler.go +++ b/src/module/dashboard_tim_ta/transport/timta_handler.go @@ -11,21 +11,22 @@ import ( "gitlab.informatika.org/k-01-11/graduit-be/src/utils" ) -type AdminDashboardTimHandler struct { +type TIMTADashboardTimHandler struct { dashboardTimUseCase usecase.DashboardTimUseCase } -func NewAdminDashboardTimHandler(cfg config.DashboardTimTransportConfig) *AdminDashboardTimHandler { +func NewTIMTADashboardTimHandler(cfg config.DashboardTimTransportConfig) *TIMTADashboardTimHandler { dashboardTimRepository := repository.NewDashboardTimRepository(cfg.DBWrite, cfg.DBRead) dashboardTimUsecase := usecase.NewDashboardTimUsecase(dashboardTimRepository) - return &AdminDashboardTimHandler{ + return &TIMTADashboardTimHandler{ dashboardTimUseCase: dashboardTimUsecase, } } -func (t *AdminDashboardTimHandler) MountAdmin(group *echo.Group) { +func (t *TIMTADashboardTimHandler) MountTIMTA(group *echo.Group) { group.GET("/dashboard-tim", t.GetDataMahasiswa) + group.GET("/dashboard-status", t.GetStatusMahasiswa) } // GetDataMahasiswa retrieves a list of mahasiswa that has already done pendaftaran @@ -39,7 +40,7 @@ func (t *AdminDashboardTimHandler) MountAdmin(group *echo.Group) { // @Param offset query string false "Offset" // @Success 200 {object} []entity.DataPendaftaran "List mahasiswa successfully retrieved" // @Router /dashboard-tim [get] -func (t *AdminDashboardTimHandler) GetDataMahasiswa(c echo.Context) error { +func (t *TIMTADashboardTimHandler) GetDataMahasiswa(c echo.Context) error { sizeStr := c.QueryParam("limit") offsetStr := c.QueryParam("offset") @@ -63,3 +64,28 @@ func (t *AdminDashboardTimHandler) GetDataMahasiswa(c echo.Context) error { return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "List mahasiswa successfully retreived", listMahasiswa)) } + +func (t *TIMTADashboardTimHandler) GetStatusMahasiswa(c echo.Context) error { + sizeStr := c.QueryParam("limit") + offsetStr := c.QueryParam("offset") + + size, err := strconv.Atoi(sizeStr) + if err != nil { + // Handle error, maybe return an error response + size = 10 + } + + offset, err := strconv.Atoi(offsetStr) + if err != nil { + // Handle error, maybe return an error response + offset = 0 + } + + listMahasiswa, err := t.dashboardTimUseCase.GetStatusMahasiswa(offset, size) + if err != nil { + return c.JSON(http.StatusBadRequest, utils.ResponseDetailOutput(false, http.StatusBadRequest, err.Error(), nil)) + } + + return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "List Status mahasiswa successfully retreived", listMahasiswa)) + +} \ No newline at end of file diff --git a/src/module/pendaftaran/entity/pendaftaran.go b/src/module/pendaftaran/entity/pendaftaran.go index 70e72a016b887de30d7d50ce067a53515fb44190..667ccc0337859090b34b5826b84be4fb9d9a4596 100644 --- a/src/module/pendaftaran/entity/pendaftaran.go +++ b/src/module/pendaftaran/entity/pendaftaran.go @@ -59,6 +59,47 @@ type RekapPendaftaran struct { InterviewAt time.Time `json:"jadwal_interview"` } +type RekapPendaftaranTimta struct { + ID string `json:"mahasiswa_id"` + Nama string `json:"mahasiswa_nama"` + Nim string `json:"nim"` + PembimbingNama string `json:"pembimbing_nama"` + Status string `json:"status"` + PendaftaranId string `json:"pendaftaran_id"` +} + +type RiwayatPendaftaran struct { + ID string `json:"id"` + InterviewAt string `json:"jadwalInterview"` + JalurPilihan string `json:"jalurPilihan"` + WaktuPengiriman string `json:"waktuPengiriman"` + Status string `json:"status"` + Judul string `json:"judulTopik"` + Deskripsi string `json:"deskripsiTopik"` +} + +type DosenPembimbing struct { + IDPendaftaran string `json:"pendaftaran_id"` + ID string `json:"id"` + Nama string `json:"nama"` +} + +type DosenPembimbings struct { + ID string `json:"id"` + Nama string `json:"nama"` + Email string `json:"email"` +} +type RiwayatPendaftaranRes struct { + ID string `json:"id"` + InterviewAt string `json:"jadwalInterview"` + JalurPilihan string `json:"jalurPilihan"` + WaktuPengiriman string `json:"waktuPengiriman"` + Status string `json:"status"` + Judul string `json:"judulTopik"` + Deskripsi string `json:"deskripsiTopik"` + DosenPembimbing []DosenPembimbing `json:"dosenPembimbing"` +} + type DetailRekapPendaftara struct { Judul string `json:"judulTopik"` Deskripsi string `json:"deskripsiTopik"` @@ -78,6 +119,16 @@ type UpdateStatusReq struct { Status string `json:"status"` } +type UpdateDosbingReq struct { + IdDosen []string `json:"pembimbing_ids"` + IDPendaftaran string `json:"id_pendaftaran"` +} + +type UpdateStatusByIdReq struct { + IDPendaftaran string `json:"id_pendaftaran"` + Status string `json:"status"` +} + type Stats struct { Diterima StatsItem `json:"diterima"` SedangProses StatsItem `json:"sedang_proses"` diff --git a/src/module/pendaftaran/internal/repository/pendaftaran.go b/src/module/pendaftaran/internal/repository/pendaftaran.go index 78303f4b91741ea2febd81ed5e2a169d7efc754f..cbc80aa9dc053a91577c9edd90e13499e1fab7a9 100644 --- a/src/module/pendaftaran/internal/repository/pendaftaran.go +++ b/src/module/pendaftaran/internal/repository/pendaftaran.go @@ -210,6 +210,43 @@ func (repo *PendaftaranRepo) UpdateStatus(status string, idDosen string, idMahas return nil } +func (repo *PendaftaranRepo) UpdateStatusById(status string, idPendaftaran string) error { + updates := map[string]interface{}{ + "status": status, + } + + if err := repo.DBWrite.Table("pendaftaran_ta"). + Where("id = ?", idPendaftaran). + Updates(updates).Error; err != nil { + return err + } + + return nil +} + +func (repo *PendaftaranRepo) UpdateDosbing(idDosbing string, idPendaftaran string) error { + pendaftaranTaUpdates := map[string]interface{}{ + "id_dosen": idDosbing, + } + dosenBimbinganUpdate := map[string]interface{}{ + "dosen_id": idDosbing, + } + + if err := repo.DBWrite.Table("pendaftaran_ta"). + Where("id = ?", idPendaftaran). + Updates(pendaftaranTaUpdates).Error; err != nil { + return err + } + + if err := repo.DBWrite.Table("dosen_bimbingan"). + Where("pendaftaran_id = ?", idPendaftaran). + Updates(dosenBimbinganUpdate).Error; err != nil { + return err + } + + return nil +} + func (repo *PendaftaranRepo) CountApproved(idDosen string) (int64, error) { var count int64 @@ -259,4 +296,114 @@ func (repo *PendaftaranRepo) GetListofDosbing() ([]entity.Pengguna, error) { } return dosbings, nil -} \ No newline at end of file +} + +func (repo *PendaftaranRepo) CountApprovedAll() (int64, error) { + var count int64 + + if err := repo.DBRead.Table("pendaftaran_ta"). + Where("status = ?", "APPROVED"). + Count(&count).Error; err != nil { + return 0, err + } + + return count, nil +} +func (repo *PendaftaranRepo) CountProsesAll() (int64, error) { + var count int64 + + if err := repo.DBRead.Table("pendaftaran_ta"). + Where("status = ? ", "NOT_ASSIGNED"). + Or("status = ? ", "INTERVIEW"). + Count(&count).Error; err != nil { + return 0, err + } + + return count, nil +} + +func (repo *PendaftaranRepo) CountRejectedAll() (int64, error) { + var count int64 + + if err := repo.DBRead.Table("pendaftaran_ta"). + Where("status = ?", "REJECTED"). + Count(&count).Error; err != nil { + return 0, err + } + + return count, nil +} + +func (repo *PendaftaranRepo) GetDosbingName(idMahasiswa string) (string, error) { + var dosbingName string + + query := ` + SELECT p.nama + FROM pengguna p + JOIN dosen_bimbingan db ON p.id = db.dosen_id + JOIN pendaftaran_ta pt ON db.pendaftaran_id = pt.id + WHERE pt.id_mahasiswa = ? + ORDER BY pt.waktu_pengiriman DESC + LIMIT 1; + ` + + if err := repo.DBRead.Raw(query, idMahasiswa).Scan(&dosbingName).Error; err != nil { + return "", err + } + + return dosbingName, nil +} + +func (repo *PendaftaranRepo) GetAllPendaftaran() ([]entity.RekapPendaftaran, error) { + var res []entity.RekapPendaftaran + err := repo.DBRead.Table("pendaftaran_ta"). + Select("pendaftaran_ta.id, pendaftaran_ta.id_mahasiswa, pendaftaran_ta.status, pendaftaran_ta.interview_at, pengguna.nama, pengguna.nim"). + Joins("JOIN pengguna on pendaftaran_ta.id_mahasiswa = pengguna.id"). + Scan(&res).Error + if err != nil { + return nil, err + } + + return res, nil +} + +func (repo *PendaftaranRepo) GetRiwayatPendaftaran(idMahasiswa string) ([]entity.RiwayatPendaftaran, error) { + var res []entity.RiwayatPendaftaran + err := repo.DBRead.Table("pendaftaran_ta"). + Select("pendaftaran_ta.id, pendaftaran_ta.jalur_pilihan, pendaftaran_ta.status, pendaftaran_ta.interview_at, pendaftaran_ta.waktu_pengiriman, topik.judul, topik.deskripsi"). + Joins("JOIN topik on pendaftaran_ta.id_topik = topik.id"). + Where("pendaftaran_ta.id_mahasiswa = ?", idMahasiswa). + Scan(&res).Error + if err != nil { + return nil, err + } + + return res, nil +} + +func (repo *PendaftaranRepo) GetDosbingForPendaftaran(idMahasiswa string) ([]entity.DosenPembimbing, error) { + var res []entity.DosenPembimbing + err := repo.DBRead.Table("pendaftaran_ta"). + Select("pendaftaran_ta.id as id_pendaftaran, pengguna.id, pengguna.nama"). + Joins("JOIN pengguna on pendaftaran_ta.id_dosen = pengguna.id"). + Where("pendaftaran_ta.id_mahasiswa = ?", idMahasiswa). + Scan(&res).Error + if err != nil { + return nil, err + } + + return res, nil +} + +func (repo *PendaftaranRepo) GetAllDosbing() ([]entity.DosenPembimbings, error) { + var res []entity.DosenPembimbings + err := repo.DBRead.Table("pengguna"). + Select("pengguna.id, pengguna.nama, pengguna.email"). + Where("? = ANY(pengguna.roles)", "S1_PEMBIMBING"). + Scan(&res).Error + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/src/module/pendaftaran/internal/usecase/pendaftaran.go b/src/module/pendaftaran/internal/usecase/pendaftaran.go index 0f138bcfc2f79512c41a35717e9f41a883343bcd..d365212d779f5208e0fd24f1dc34c392b6c89908 100644 --- a/src/module/pendaftaran/internal/usecase/pendaftaran.go +++ b/src/module/pendaftaran/internal/usecase/pendaftaran.go @@ -23,6 +23,12 @@ type PendaftaranUsecase interface { UpdateStatus(status string, idDosen string, idMahasiswa string) error GetStatisticsPendaftaran(idDosen string) (entity.Stats, error) GetListofDosbing() ([]entity.Pengguna, error) + GetRekapPendaftaranTimta() ([]entity.RekapPendaftaranTimta, error) + GetStatisticsPendaftaranTimta() (entity.Stats, error) + UpdateStatusById(status string, idPendaftaran string) error + GetRiwayatPendaftaran(idMahasiswa string) ([]entity.RiwayatPendaftaranRes, error) + GetAllDosbing() ([]entity.DosenPembimbings, error) + UpdateDosbing(idDosen []string, idPendaftaran string) error } type PendaftaranUc struct { @@ -82,6 +88,10 @@ func (uc *PendaftaranUc) GetDetailRekapPendafataran(idDosen string, idMahasiswa return uc.pendaftaranRepo.GetDetailRekapPendafataran(idDosen, idMahasiswa) } +func (uc *PendaftaranUc) GetAllDosbing() ([]entity.DosenPembimbings, error) { + return uc.pendaftaranRepo.GetAllDosbing() +} + func (uc *PendaftaranUc) UpdateInterview(interviewAt string, idDosen string, idMahasiswa string) error { if interviewAt == "" { return errors.New("interview timestamp cannot be empty") @@ -96,6 +106,87 @@ func (uc *PendaftaranUc) UpdateStatus(status string, idDosen string, idMahasiswa return uc.pendaftaranRepo.UpdateStatus(status, idDosen, idMahasiswa) } +func (uc *PendaftaranUc) UpdateDosbing(idDosen []string, idPendaftaran string) error { + return uc.pendaftaranRepo.UpdateDosbing(idDosen[0], idPendaftaran) +} + +func (uc *PendaftaranUc) UpdateStatusById(status string, idPendaftaran string) error { + if status == "" { + return errors.New("interview timestamp cannot be empty") + } + return uc.pendaftaranRepo.UpdateStatusById(status, idPendaftaran) +} + +func (uc *PendaftaranUc) GetRiwayatPendaftaran(idMahasiswa string) ([]entity.RiwayatPendaftaranRes, error) { + // Retrieve dosen pembimbing information + dosBing, err := uc.pendaftaranRepo.GetDosbingForPendaftaran(idMahasiswa) + if err != nil { + return []entity.RiwayatPendaftaranRes{}, err + } + + // Retrieve riwayat pendaftaran + riwPen, err := uc.pendaftaranRepo.GetRiwayatPendaftaran(idMahasiswa) + if err != nil { + return []entity.RiwayatPendaftaranRes{}, err + } + + var riwayatPendaftarans []entity.RiwayatPendaftaranRes + for _, riw := range riwPen { + var dosenPembimbings []entity.DosenPembimbing + + for _, dos := range dosBing { + if dos.IDPendaftaran == riw.ID { + dosenPembimbing := entity.DosenPembimbing{ + ID: dos.ID, + Nama: dos.Nama, + } + dosenPembimbings = append(dosenPembimbings, dosenPembimbing) + } + } + + // Create the RiwayatPendaftaranRes struct + riwayatPendaftaran := entity.RiwayatPendaftaranRes{ + ID: riw.ID, + InterviewAt: riw.InterviewAt, + JalurPilihan: riw.JalurPilihan, + WaktuPengiriman: riw.WaktuPengiriman, + Status: riw.Status, + Judul: riw.Judul, + Deskripsi: riw.Deskripsi, + DosenPembimbing: dosenPembimbings, + } + + riwayatPendaftarans = append(riwayatPendaftarans, riwayatPendaftaran) + } + + return riwayatPendaftarans, nil +} + +func (uc *PendaftaranUc) GetRekapPendaftaranTimta() ([]entity.RekapPendaftaranTimta, error) { + var rekapPendaftarans []entity.RekapPendaftaranTimta + allPendaftaran, err := uc.pendaftaranRepo.GetAllPendaftaran() + if err != nil { + return []entity.RekapPendaftaranTimta{}, nil + } + for _, pendaftaran := range allPendaftaran { + dosbingName, err := uc.pendaftaranRepo.GetDosbingName(pendaftaran.IDMahasiswa) + if err != nil { + return []entity.RekapPendaftaranTimta{}, nil + } + rekapPendaftaran := entity.RekapPendaftaranTimta{ + ID: pendaftaran.IDMahasiswa, + Nama: pendaftaran.Nama, + Nim: pendaftaran.Nim, + PembimbingNama: dosbingName, + Status: pendaftaran.Status, + PendaftaranId: pendaftaran.ID, + } + rekapPendaftarans = append(rekapPendaftarans, rekapPendaftaran) + } + return rekapPendaftarans, nil + +} + func (uc *PendaftaranUc) GetStatisticsPendaftaran(idDosen string) (entity.Stats, error) { approved, err := uc.pendaftaranRepo.CountApproved(idDosen) if err != nil { @@ -140,4 +231,41 @@ func (uc *PendaftaranUc) GetListofDosbing() ([]entity.Pengguna, error) { } return dosbings, nil +} + +func (uc *PendaftaranUc) GetStatisticsPendaftaranTimta() (entity.Stats, error) { + approved, err := uc.pendaftaranRepo.CountApprovedAll() + if err != nil { + return entity.Stats{}, err + } + proses, err := uc.pendaftaranRepo.CountProsesAll() + if err != nil { + return entity.Stats{}, err + } + rejected, err := uc.pendaftaranRepo.CountRejectedAll() + if err != nil { + return entity.Stats{}, err + } + total := approved + proses + rejected + percApproved := approved / total * 100 + percProses := proses / total * 100 + perRejected := rejected / total * 100 + diterima := entity.StatsItem{ + Amount: approved, + Percentage: percApproved, + } + sedangProses := entity.StatsItem{ + Amount: proses, + Percentage: percProses, + } + ditolak := entity.StatsItem{ + Amount: rejected, + Percentage: perRejected, + } + stats := entity.Stats{ + Diterima: diterima, + SedangProses: sedangProses, + Ditolak: ditolak, + } + return stats, nil } \ No newline at end of file diff --git a/src/module/pendaftaran/internal/usecase/repository.go b/src/module/pendaftaran/internal/usecase/repository.go index b900987219e61c640200eca9a95e22e5a25185ad..53cd676c6bc26ac0e002daf1273dbabd4f51d19e 100644 --- a/src/module/pendaftaran/internal/usecase/repository.go +++ b/src/module/pendaftaran/internal/usecase/repository.go @@ -24,4 +24,14 @@ type PendaftaranRepository interface { CountProses(idDosen string) (int64, error) CountRejected(idDosen string) (int64, error) GetListofDosbing() ([]entity.Pengguna, error) + CountApprovedAll() (int64, error) + CountProsesAll() (int64, error) + CountRejectedAll() (int64, error) + GetDosbingName(idMahasiswa string) (string, error) + GetAllPendaftaran() ([]entity.RekapPendaftaran, error) + UpdateStatusById(status string, idPendaftaran string) error + GetRiwayatPendaftaran(idMahasiswa string) ([]entity.RiwayatPendaftaran, error) + GetDosbingForPendaftaran(idMahasiswa string) ([]entity.DosenPembimbing, error) + GetAllDosbing() ([]entity.DosenPembimbings, error) + UpdateDosbing(idDosbing string, idPendaftaran string) error } diff --git a/src/module/pendaftaran/transport/admin_handler.go b/src/module/pendaftaran/transport/admin_handler.go index 2ea37f30137c6fbb8ba1508b55f59b5337fd7595..63e0d96fc839301e0fcd874ef1aa3d72907b745f 100644 --- a/src/module/pendaftaran/transport/admin_handler.go +++ b/src/module/pendaftaran/transport/admin_handler.go @@ -36,11 +36,17 @@ func (t *AdminPendaftaranHandler) MountAdmin(group *echo.Group) { group.GET("/pendaftaran-by-periode", t.GetPendaftaranByPeriode) group.GET("/pendaftaran-by-id", t.GetPendaftaranById) group.GET("/rekap-pendaftaran", t.GetRekapPendaftaranDosen) + group.GET("/rekap-pendaftaran-timta", t.GetRekapPendaftaranTimta) group.GET("/detail-rekap-pendaftaran", t.GetDetailRekapPendafataran) group.PUT("/update-interview", t.UpdateInterview) group.PUT("/update-status", t.UpdateStatus) + group.PUT("/update-status-by-id", t.UpdateStatusById) group.GET("/statistics", t.GetStatistics) group.GET("/dosen-bimbingan", t.GetListofDosbing) + group.GET("/statistics-timta", t.GetStatisticsTimta) + group.GET("/riwayat-pendaftaran", t.GetRiwayatPendaftaran) + group.GET("/all-dosbing", t.GetAllDosbing) + group.PUT("/update-dosbing", t.UpdateDosbing) } // AddPendaftaran adds a new pendaftaran record. @@ -90,6 +96,15 @@ func (t *AdminPendaftaranHandler) GetPendaftaran(c echo.Context) error { return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "Pendaftaran successfully retreived", listGroupLimit)) } +func (t *AdminPendaftaranHandler) GetAllDosbing(c echo.Context) error { + dosbings, err := t.pendaftaranUsecase.GetAllDosbing() + if err != nil { + return c.JSON(http.StatusBadRequest, utils.ResponseDetailOutput(false, http.StatusBadRequest, err.Error(), nil)) + } + + return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "All dosbing successfully retreived", dosbings)) +} + func (t *AdminPendaftaranHandler) GetPendaftaranByIdDosen(c echo.Context) error { payload, ok := c.Get("authPayload").(utils.Payload) if !ok { @@ -116,6 +131,25 @@ func (t *AdminPendaftaranHandler) GetRekapPendaftaranDosen(c echo.Context) error return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "Rekap successfully retreived", rekap)) } +func (t *AdminPendaftaranHandler) GetRekapPendaftaranTimta(c echo.Context) error { + rekap, err := t.pendaftaranUsecase.GetRekapPendaftaranTimta() + if err != nil { + return c.JSON(http.StatusBadRequest, utils.ResponseDetailOutput(false, http.StatusBadRequest, err.Error(), nil)) + } + + return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "Rekap successfully retreived", rekap)) +} + +func (t *AdminPendaftaranHandler) GetRiwayatPendaftaran(c echo.Context) error { + idMahasiswa := c.QueryParam("id_mahasiswa") + riwayat, err := t.pendaftaranUsecase.GetRiwayatPendaftaran(idMahasiswa) + if err != nil { + return c.JSON(http.StatusBadRequest, utils.ResponseDetailOutput(false, http.StatusBadRequest, err.Error(), nil)) + } + + return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "riwayat successfully retreived", riwayat)) +} + func (t *AdminPendaftaranHandler) GetDetailRekapPendafataran(c echo.Context) error { payload, ok := c.Get("authPayload").(utils.Payload) if !ok { @@ -164,6 +198,32 @@ func (t *AdminPendaftaranHandler) UpdateStatus(c echo.Context) error { return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "Interview successfully updated", err)) } +func (t *AdminPendaftaranHandler) UpdateDosbing(c echo.Context) error { + request := new(entity.UpdateDosbingReq) + if err := c.Bind(&request); err != nil { + return c.JSON(http.StatusBadRequest, utils.ResponseDetailOutput(false, http.StatusBadRequest, err.Error(), nil)) + } + err := t.pendaftaranUsecase.UpdateDosbing(request.IdDosen, request.IDPendaftaran) + if err != nil { + return c.JSON(http.StatusBadRequest, utils.ResponseDetailOutput(false, http.StatusBadRequest, err.Error(), nil)) + } + + return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "Interview successfully updated", err)) +} + +func (t *AdminPendaftaranHandler) UpdateStatusById(c echo.Context) error { + request := new(entity.UpdateStatusByIdReq) + if err := c.Bind(&request); err != nil { + return c.JSON(http.StatusBadRequest, utils.ResponseDetailOutput(false, http.StatusBadRequest, err.Error(), nil)) + } + err := t.pendaftaranUsecase.UpdateStatusById(request.Status, request.IDPendaftaran) + if err != nil { + return c.JSON(http.StatusBadRequest, utils.ResponseDetailOutput(false, http.StatusBadRequest, err.Error(), nil)) + } + + return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "Interview successfully updated", err)) +} + func (t *AdminPendaftaranHandler) GetStatistics(c echo.Context) error { payload, ok := c.Get("authPayload").(utils.Payload) if !ok { @@ -177,6 +237,15 @@ func (t *AdminPendaftaranHandler) GetStatistics(c echo.Context) error { return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "stats successfully updated", stats)) } +func (t *AdminPendaftaranHandler) GetStatisticsTimta(c echo.Context) error { + stats, err := t.pendaftaranUsecase.GetStatisticsPendaftaranTimta() + if err != nil { + return c.JSON(http.StatusBadRequest, utils.ResponseDetailOutput(false, http.StatusBadRequest, err.Error(), nil)) + } + + return c.JSON(http.StatusOK, utils.ResponseDetailOutput(true, http.StatusOK, "stats successfully updated", stats)) +} + func (t *AdminPendaftaranHandler) GetPendaftaranById(c echo.Context) error { id := c.QueryParam("id") listGroupLimit, err := t.pendaftaranUsecase.GetPendaftaranById(id) diff --git a/src/module/pendaftaran_sidsem/entity/pendaftaran_sidsem.go b/src/module/pendaftaran_sidsem/entity/pendaftaran_sidsem.go index 034e9a6731092e05b88268a841d5e0709bcccbfa..29fadb710193c6855427c4493b083f442b241d56 100644 --- a/src/module/pendaftaran_sidsem/entity/pendaftaran_sidsem.go +++ b/src/module/pendaftaran_sidsem/entity/pendaftaran_sidsem.go @@ -191,17 +191,17 @@ type RemainingDetailSidSem struct { } type DetailSidSemByMahasiswa struct { - IDMahasiswa string `json:"id_mahasiswa"` - Nama string `json:"nama"` - Email string `json:"email"` - JalurPilihan string `json:"jalur_pilihan"` - Judul string `json:"judul"` - Deskripsi string `json:"deskripsi"` - DosbingName string `json:"dosbing_name"` - Tipe string `json:"tipe"` - WaktuMulai time.Time `json:"waktu_mulai"` - NamaRuangan string `json:"nama_ruangan"` - Ditolak bool `json:"ditolak"` + IDMahasiswa string `json:"id_mahasiswa"` + Nama string `json:"nama"` + Email string `json:"email"` + JalurPilihan string `json:"jalur_pilihan"` + Judul string `json:"judul"` + Deskripsi string `json:"deskripsi"` + DosbingName string `json:"dosbing_name"` + Tipe string `json:"tipe"` + WaktuMulai string `json:"waktu_mulai"` + NamaRuangan string `json:"nama_ruangan"` + Ditolak bool `json:"ditolak"` } type DetailSidSemTA struct { @@ -215,7 +215,7 @@ type DetailSidSemTA struct { JudulProposal string `json:"judul_proposal"` Deskripsi string `json:"deskripsi"` Berkas []BerkasSidSem `json:"berkas_sidsem"` - JadwalSidang time.Time `json:"jadwal_sidang"` + JadwalSidang string `json:"jadwal_sidang"` Tempat string `json:"tempat"` Status bool `json:"status"` } diff --git a/src/module/pendaftaran_sidsem/internal/usecase/pendaftaran_sidsem.go b/src/module/pendaftaran_sidsem/internal/usecase/pendaftaran_sidsem.go index 642735351c6f0a5a4ef0789428a073ded873fb9e..b23baa7c392613b29fd24ca3af34432a9d1d846e 100644 --- a/src/module/pendaftaran_sidsem/internal/usecase/pendaftaran_sidsem.go +++ b/src/module/pendaftaran_sidsem/internal/usecase/pendaftaran_sidsem.go @@ -160,6 +160,7 @@ func (uc *PendaftaranSidSemUc) GetPlaceholders(idMahasiswa string) (entity.Place func (uc *PendaftaranSidSemUc) GetSidSemDetailByMahasiswaId(idMahasiswa string, tipe string) (entity.DetailSidSemByMahasiswa, error) { var namaRuangan string + var waktuMulai string infoTopik, err := uc.pendaftaransidsemrepo.GetInfoTopikMahasiswa(idMahasiswa) if err != nil { return entity.DetailSidSemByMahasiswa{}, err @@ -188,7 +189,11 @@ func (uc *PendaftaranSidSemUc) GetSidSemDetailByMahasiswaId(idMahasiswa string, return entity.DetailSidSemByMahasiswa{}, err } } - + if remDet.WaktuMulai.IsZero() { + waktuMulai = "" + } else { + waktuMulai = remDet.WaktuMulai.String() + } detailSidSemByMahasiswa := entity.DetailSidSemByMahasiswa{ IDMahasiswa: infoTopik.IDMahasiswa, Nama: infoTopik.Nama, @@ -198,7 +203,7 @@ func (uc *PendaftaranSidSemUc) GetSidSemDetailByMahasiswaId(idMahasiswa string, Deskripsi: infoTopik.Deskripsi, DosbingName: dosbingName.Nama, Tipe: remDet.Tipe, - WaktuMulai: remDet.WaktuMulai, + WaktuMulai: waktuMulai, NamaRuangan: namaRuangan, Ditolak: remDet.Ditolak, } @@ -209,6 +214,7 @@ func (uc *PendaftaranSidSemUc) GetSidSemDetailByMahasiswaId(idMahasiswa string, func (uc *PendaftaranSidSemUc) GetSidSemDetailTA(idSidsem string) (entity.DetailSidSemTA, error) { var namaRuangan string + var waktuMulai string idMahasiswa, err := uc.pendaftaransidsemrepo.GetIdMahasiswaFromIdSidSem(idSidsem) if err != nil { return entity.DetailSidSemTA{}, err @@ -254,6 +260,11 @@ func (uc *PendaftaranSidSemUc) GetSidSemDetailTA(idSidsem string) (entity.Detail if len(dosujiNames) == 0 { dosujiNames = []entity.Dosuji{} } + if remDet.WaktuMulai.IsZero() { + waktuMulai = "" + } else { + waktuMulai = remDet.WaktuMulai.String() + } detailSidsemTA := entity.DetailSidSemTA{ IDMahasiswa: infoTopik.IDMahasiswa, Nama: infoTopik.Nama, @@ -265,7 +276,7 @@ func (uc *PendaftaranSidSemUc) GetSidSemDetailTA(idSidsem string) (entity.Detail JudulProposal: judDes.JudulProposal, Deskripsi: judDes.Deskripsi, Berkas: berkasSidsem, - JadwalSidang: remDet.WaktuMulai, + JadwalSidang: waktuMulai, Tempat: namaRuangan, Status: remDet.Ditolak, }