Compare commits

..

3 Commits

Author SHA1 Message Date
Alex
743493517b Backend: add Subscription CRUD 2025-02-12 20:30:59 +01:00
Alex
8787c8c2c1 delete static remnants 2025-02-12 20:30:25 +01:00
Alex
993d7920d4 Frontend: Locale 2025-02-12 20:30:12 +01:00
37 changed files with 347 additions and 37834 deletions

View File

@@ -56,7 +56,8 @@ export default {
jwt_parsing_error: 'Nicht authorisiert, Auth-Token konnte nicht gelesen werden',
unauthorized: 'Sie sind nicht befugt diese Handlung durchzuführen',
internal_server_error:
'Verdammt, Fehler auf unserer Seite, probieren Sie es nochmal, danach rufen Sie jemanden vom Verein an.'
'Verdammt, Fehler auf unserer Seite, probieren Sie es nochmal, danach rufen Sie jemanden vom Verein an.',
not_possible: 'Vorgang nicht möglich.'
},
validation: {
invalid_user_id: 'Nutzer ID ungültig',

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,426 +0,0 @@
/*!
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
background-color: #fff;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
direction: ltr /* rtl:ignore */;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +0,0 @@
/*!
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -84,10 +84,9 @@ func (mc *MembershipController) UpdateHandler(c *gin.Context) {
return
}
// Register Subscription
logger.Info.Printf("Registering subscription %v", regData.Subscription.Name)
// id, err := mc.Service.UpdateSubscription(&regData.Subscription)
id := 1
// update Subscription
logger.Info.Printf("Updating subscription %v", regData.Subscription.Name)
id, err := mc.Service.UpdateSubscription(&regData.Subscription)
if err != nil {
logger.Error.Printf("Couldn't update Membershipmodel: %v", err)
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
@@ -103,6 +102,33 @@ func (mc *MembershipController) UpdateHandler(c *gin.Context) {
"id": id,
})
}
func (mc *MembershipController) DeleteSubscription(c *gin.Context) {
var membershipdata MembershipData
requestUser, err := mc.UserController.ExtractUserFromContext(c)
if err != nil {
utils.RespondWithError(c, err, "Error extracting user from context in subscription UpdateHandler", http.StatusBadRequest, "general", "server.validation.no_auth_tokenw")
return
}
if !utils.HasPrivilige(requestUser, constants.Priviliges.Update) {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update subscription", http.StatusForbidden, "user", "server.error.unauthorized")
return
}
if err := c.ShouldBindJSON(&membershipdata); err != nil {
utils.HandleValidationError(c, err)
return
}
if err := mc.Service.DeleteSubscription(&membershipdata.Subscription); err != nil {
utils.RespondWithError(c, err, "Error during subscription Deletion", http.StatusExpectationFailed, "subscription", "server.error.not_possible")
return
}
c.JSON(http.StatusOK, gin.H{"message": "Subscription deleted successfully"})
}
func (mc *MembershipController) GetSubscriptions(c *gin.Context) {
subscriptions, err := mc.Service.GetSubscriptions(nil)
if err != nil {

View File

@@ -21,6 +21,22 @@ type RegisterSubscriptionTest struct {
Assert bool
}
type UpdateSubscriptionTest struct {
WantDBData map[string]interface{}
Input string
Name string
WantResponse int
Assert bool
}
type DeleteSubscriptionTest struct {
WantDBData map[string]interface{}
Input string
Name string
WantResponse int
Assert bool
}
type MockUserController struct {
UserController // Embed the UserController
}
@@ -44,7 +60,7 @@ func setupMockAuth() {
func testMembershipController(t *testing.T) {
setupMockAuth()
tests := getSubscriptionData()
tests := getSubscriptionRegistrationData()
for _, tt := range tests {
logger.Error.Print("==============================================================")
logger.Error.Printf("MembershipController : %v", tt.Name)
@@ -55,6 +71,28 @@ func testMembershipController(t *testing.T) {
}
})
}
updateTests := getSubscriptionUpdateData()
for _, tt := range updateTests {
logger.Error.Print("==============================================================")
logger.Error.Printf("Update SubscriptionData : %v", tt.Name)
logger.Error.Print("==============================================================")
t.Run(tt.Name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
deleteTests := getSubscriptionDeleteData()
for _, tt := range deleteTests {
logger.Error.Print("==============================================================")
logger.Error.Printf("Delete SubscriptionData : %v", tt.Name)
logger.Error.Print("==============================================================")
t.Run(tt.Name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
}
func (rt *RegisterSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
@@ -87,6 +125,44 @@ func validateSubscription(assert bool, wantDBData map[string]interface{}) error
return nil
}
func (ut *UpdateSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
return GetMockedJSONContext([]byte(ut.Input), "api/subscription/upsert")
}
func (ut *UpdateSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
Mc.UpdateHandler(c)
}
func (ut *UpdateSubscriptionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if w.Code != ut.WantResponse {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, ut.WantResponse)
}
return nil
}
func (ut *UpdateSubscriptionTest) ValidateResult() error {
return validateSubscription(ut.Assert, ut.WantDBData)
}
func (dt *DeleteSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
return GetMockedJSONContext([]byte(dt.Input), "api/subscription/delete")
}
func (dt *DeleteSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
Mc.DeleteSubscription(c)
}
func (dt *DeleteSubscriptionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if w.Code != dt.WantResponse {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, dt.WantResponse)
}
return nil
}
func (dt *DeleteSubscriptionTest) ValidateResult() error {
return validateSubscription(dt.Assert, dt.WantDBData)
}
func getBaseSubscription() MembershipData {
return MembershipData{
// APIKey: config.Auth.APIKEY,
@@ -103,7 +179,7 @@ func customizeSubscription(customize func(MembershipData) MembershipData) Member
return customize(subscription)
}
func getSubscriptionData() []RegisterSubscriptionTest {
func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
return []RegisterSubscriptionTest{
{
Name: "Missing details should fail",
@@ -169,3 +245,137 @@ func getSubscriptionData() []RegisterSubscriptionTest {
},
}
}
func getSubscriptionUpdateData() []UpdateSubscriptionTest {
return []UpdateSubscriptionTest{
{
Name: "Modified Monthly Fee, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium", "monthly_fee": "12"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.MonthlyFee = 123.0
return subscription
})),
},
{
Name: "Missing ID, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.ID = 0
return subscription
})),
},
{
Name: "Modified Hourly Rate, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium", "hourly_rate": "14"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.HourlyRate = 3254.0
return subscription
})),
},
{
Name: "IncludedPerYear changed, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium", "included_per_year": "0"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.IncludedPerYear = 9873.0
return subscription
})),
},
{
Name: "IncludedPerMonth changed, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium", "included_per_month": "1"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.IncludedPerMonth = 23415.0
return subscription
})),
},
{
Name: "Update non-existent subscription should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = "NonExistentSubscription"
return subscription
})),
},
{
Name: "Correct Update should pass",
WantResponse: http.StatusAccepted,
WantDBData: map[string]interface{}{"name": "Premium", "details": "Altered Details"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Details = "Altered Details"
subscription.Subscription.Conditions = "Some Condition"
subscription.Subscription.IncludedPerYear = 0
subscription.Subscription.IncludedPerMonth = 1
return subscription
})),
},
}
}
func getSubscriptionDeleteData() []DeleteSubscriptionTest {
return []DeleteSubscriptionTest{
{
Name: "Delete non-existent subscription should fail",
WantResponse: http.StatusExpectationFailed,
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = "NonExistentSubscription"
return subscription
})),
},
{
Name: "Delete subscription without name should fail",
WantResponse: http.StatusBadRequest,
WantDBData: map[string]interface{}{"name": ""},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = ""
return subscription
})),
},
{
Name: "Delete subscription with users should fail",
WantResponse: http.StatusExpectationFailed,
WantDBData: map[string]interface{}{"name": "Basic"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = "Basic"
return subscription
})),
},
{
Name: "Delete valid subscription should succeed",
WantResponse: http.StatusOK,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = "Premium"
return subscription
})),
},
}
}

View File

@@ -212,7 +212,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
}
logger.Info.Printf("Registering user %v", regData.User.Email)
selectedModel, err := uc.MembershipService.GetModelByName(&regData.User.Membership.SubscriptionModel.Name)
selectedModel, err := uc.MembershipService.GetSubscriptionByName(&regData.User.Membership.SubscriptionModel.Name)
if err != nil {
utils.RespondWithError(c, err, "Error in Registeruser, couldn't get selected model", http.StatusNotFound, "subscription_model", "server.validation.subscription_model_not_found")
return

View File

@@ -10,8 +10,11 @@ import (
type SubscriptionModelsRepositoryInterface interface {
CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (uint, error)
GetMembershipModelNames() ([]string, error)
UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error)
GetSubscriptionModelNames() ([]string, error)
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
// GetUsersBySubscription(id uint) (*[]models.SubscriptionModel, error)
DeleteSubscription(subscription *models.SubscriptionModel) error
}
type SubscriptionModelsRepository struct{}
@@ -25,15 +28,34 @@ func (sr *SubscriptionModelsRepository) CreateSubscriptionModel(subscriptionMode
return subscriptionModel.ID, nil
}
func GetModelByName(modelname *string) (*models.SubscriptionModel, error) {
func (sr *SubscriptionModelsRepository) UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error) {
result := database.DB.Model(&models.SubscriptionModel{ID: subscription.ID}).Updates(subscription)
if result.Error != nil {
return nil, result.Error
}
return subscription, nil
}
func (sr *SubscriptionModelsRepository) DeleteSubscription(subscription *models.SubscriptionModel) error {
result := database.DB.Delete(&models.SubscriptionModel{}, subscription.ID)
if result.Error != nil {
return result.Error
}
return nil
}
func GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error) {
var model models.SubscriptionModel
if err := database.DB.Where("name = ?", modelname).First(&model).Error; err != nil {
return nil, err
result := database.DB.Where("name = ?", modelname).First(&model)
if result.Error != nil {
return nil, result.Error
}
return &model, nil
}
func (sr *SubscriptionModelsRepository) GetMembershipModelNames() ([]string, error) {
func (sr *SubscriptionModelsRepository) GetSubscriptionModelNames() ([]string, error) {
var names []string
if err := database.DB.Model(&models.SubscriptionModel{}).Pluck("name", &names).Error; err != nil {
return []string{}, err
@@ -52,3 +74,24 @@ func (sr *SubscriptionModelsRepository) GetSubscriptions(where map[string]interf
}
return &subscriptions, nil
}
func GetUsersBySubscription(subscriptionID uint) (*[]models.User, error) {
var users []models.User
err := database.DB.Preload("Membership").
Preload("Membership.SubscriptionModel").
Preload("BankAccount").
Preload("Licence").
Preload("Licence.Categories").
Joins("JOIN memberships ON users.membership_id = memberships.id").
Joins("JOIN subscription_models ON memberships.subscription_model_id = subscription_models.id").
Where("subscription_models.id = ?", subscriptionID).
Find(&users).Error
if err != nil {
return nil, err
}
return &users, nil
}

View File

@@ -5,14 +5,17 @@ import (
"GoMembership/internal/models"
"GoMembership/internal/repositories"
"GoMembership/pkg/errors"
)
type MembershipServiceInterface interface {
RegisterMembership(membership *models.Membership) (uint, error)
FindMembershipByUserID(userID uint) (*models.Membership, error)
RegisterSubscription(subscription *models.SubscriptionModel) (uint, error)
GetMembershipModelNames() ([]string, error)
GetModelByName(modelname *string) (*models.SubscriptionModel, error)
UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error)
DeleteSubscription(subscription *models.SubscriptionModel) error
GetSubscriptionModelNames() ([]string, error)
GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error)
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
}
@@ -26,6 +29,48 @@ func (service *MembershipService) RegisterMembership(membership *models.Membersh
return service.Repo.CreateMembership(membership)
}
func (service *MembershipService) UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error) {
existingSubscription, err := repositories.GetSubscriptionByName(&subscription.Name)
if err != nil {
return nil, err
}
if existingSubscription == nil {
return nil, errors.ErrSubscriptionNotFound
}
if existingSubscription.MonthlyFee != subscription.MonthlyFee ||
existingSubscription.HourlyRate != subscription.HourlyRate ||
existingSubscription.Conditions != subscription.Conditions ||
existingSubscription.IncludedPerYear != subscription.IncludedPerYear ||
existingSubscription.IncludedPerMonth != subscription.IncludedPerMonth {
return nil, errors.ErrInvalidSubscriptionData
}
subscription.ID = existingSubscription.ID
return service.SubscriptionRepo.UpdateSubscription(subscription)
}
func (service *MembershipService) DeleteSubscription(subscription *models.SubscriptionModel) error {
exists, err := repositories.GetSubscriptionByName(&subscription.Name)
if err != nil {
return err
}
if exists == nil {
return errors.ErrNotFound
}
subscription.ID = exists.ID
usersInSubscription, err := repositories.GetUsersBySubscription(subscription.ID)
if err != nil {
return err
}
if len(*usersInSubscription) > 0 {
return errors.ErrSubscriptionInUse
}
return service.SubscriptionRepo.DeleteSubscription(subscription)
}
func (service *MembershipService) FindMembershipByUserID(userID uint) (*models.Membership, error) {
return service.Repo.FindMembershipByUserID(userID)
}
@@ -35,12 +80,12 @@ func (service *MembershipService) RegisterSubscription(subscription *models.Subs
return service.SubscriptionRepo.CreateSubscriptionModel(subscription)
}
func (service *MembershipService) GetMembershipModelNames() ([]string, error) {
return service.SubscriptionRepo.GetMembershipModelNames()
func (service *MembershipService) GetSubscriptionModelNames() ([]string, error) {
return service.SubscriptionRepo.GetSubscriptionModelNames()
}
func (service *MembershipService) GetModelByName(modelname *string) (*models.SubscriptionModel, error) {
return repositories.GetModelByName(modelname)
func (service *MembershipService) GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error) {
return repositories.GetSubscriptionByName(modelname)
}
func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) {

View File

@@ -59,7 +59,7 @@ func (service *UserService) UpdateUser(user *models.User) (*models.User, error)
}
// Validate subscription model
selectedModel, err := repositories.GetModelByName(&user.Membership.SubscriptionModel.Name)
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
if err != nil {
return nil, errors.ErrSubscriptionNotFound
}

View File

@@ -38,7 +38,7 @@ func ValidateSubscription(sl validator.StructLevel) {
}
} else {
// This is a nested probably user struct. We are only checking if the model exists
existingSubscription, err := repositories.GetModelByName(&subscription.Name)
existingSubscription, err := repositories.GetSubscriptionByName(&subscription.Name)
if err != nil || existingSubscription == nil {
sl.ReportError(subscription.Name, "Subscription_Name", "name", "exists", "")
}

View File

@@ -30,7 +30,7 @@ func validateUser(sl validator.StructLevel) {
if user.Membership.SubscriptionModel.Name == "" {
sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "required", "")
} else {
selectedModel, err := repositories.GetModelByName(&user.Membership.SubscriptionModel.Name)
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
if err != nil {
logger.Error.Printf("Error finding subscription model for user %v: %v", user.Email, err)
sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "invalid", "")