Passed
Pull Request — main (#42)
by Yume
01:15
created

queries.FetchNextCard   A

Complexity

Conditions 4

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 17
nop 2
dl 0
loc 24
rs 9.55
c 0
b 0
f 0
1
package queries
2
3
import (
4
	"errors"
5
	"fmt"
6
	"math/rand"
7
	"sort"
8
	"sync"
9
	"time"
10
11
	"github.com/memnix/memnixrest/app/models"
12
	"github.com/memnix/memnixrest/pkg/core"
13
	"github.com/memnix/memnixrest/pkg/database"
14
	"github.com/memnix/memnixrest/pkg/utils"
15
	"gorm.io/gorm"
16
)
17
18
// UpdateSubUsers generates MemDate for sub users
19
func UpdateSubUsers(card *models.Card, user *models.User) error {
20
	var users []models.User
21
	var result *models.ResponseHTTP
22
23
	if result = GetSubUsers(card.DeckID); !result.Success {
24
		log := models.CreateLog(fmt.Sprintf("Error from %s on deck %d - CreateNewCard: %s", user.Email, card.DeckID, result.Message),
25
			models.LogQueryGetError).SetType(models.LogTypeError).AttachIDs(user.ID, card.DeckID, card.ID)
26
		_ = log.SendLog()
27
		return errors.New("couldn't get sub users")
28
	}
29
30
	switch result.Data.(type) {
31
	default:
32
		return errors.New("couldn't get sub users")
33
	case []models.User:
34
		users = result.Data.([]models.User)
35
	}
36
37
	for i := range users {
38
		_ = GenerateMemDate(users[i].ID, card.ID, card.DeckID)
39
	}
40
41
	return nil
42
}
43
44
// FillResponseDeck returns a filled models.ResponseDeck
45
// This function might become a method of models.ResponseDeck
46
func FillResponseDeck(deck *models.Deck, permission models.AccessPermission, toggleToday bool) models.ResponseDeck {
47
	db := database.DBConn
48
49
	deckResponse := models.ResponseDeck{
50
		Deck:        *deck,
51
		DeckID:      deck.ID,
52
		Permission:  permission,
53
		ToggleToday: toggleToday,
54
		OwnerID:     0,
55
		Owner:       models.PublicUser{},
56
	}
57
58
	if owner := deck.GetOwner(); owner.ID != 0 {
59
		publicUser := new(models.PublicUser)
60
61
		publicUser.Set(&owner)
62
63
		deckResponse.Owner = *publicUser
64
		deckResponse.OwnerID = owner.ID
65
	}
66
67
	var count int64
68
	if err := db.Table("cards").Where("cards.deck_id = ?", deck.ID).Count(&count).Error; err != nil {
69
		deckResponse.CardCount = 0
70
	} else {
71
		deckResponse.CardCount = uint16(count)
72
	}
73
	return deckResponse
74
}
75
76
// GenerateCreatorAccess sets an user as a deck creator
77
func GenerateCreatorAccess(user *models.User, deck *models.Deck) *models.ResponseHTTP {
78
	db := database.DBConn
79
	// TODO: Change models.User & models.Deck to uint
80
	access := new(models.Access)
81
	res := new(models.ResponseHTTP)
82
83
	access.Set(user.ID, deck.ID, models.AccessOwner)
84
	db.Create(access)
85
86
	res.GenerateSuccess("Success register a creator access !", *access, 1)
87
	return res
88
}
89
90
// GenerateAccess sets a default student access to a deck for a given user
91
func GenerateAccess(user *models.User, deck *models.Deck) *models.ResponseHTTP {
92
	db := database.DBConn
93
	res := new(models.ResponseHTTP)
94
95
	if deck.Status != models.DeckPublic && user.Permissions != models.PermAdmin {
96
		res.GenerateError(utils.ErrorForbidden)
97
		return res
98
	}
99
100
	access := new(models.Access)
101
102
	if err := db.Joins("User").Joins("Deck").Where("accesses.user_id = ? AND accesses.deck_id =?", user.ID, deck.ID).Find(&access).Error; err != nil {
103
		if errors.Is(err, gorm.ErrRecordNotFound) {
104
			access.Set(user.ID, deck.ID, models.AccessStudent)
105
			db.Preload("User").Preload("Deck").Create(access)
106
		}
107
	} else {
108
		if access.Permission >= models.AccessStudent {
109
			res.GenerateError(utils.ErrorAlreadySub)
110
			return res
111
		}
112
		access.Set(user.ID, deck.ID, models.AccessStudent)
113
		db.Preload("User").Preload("Deck").Save(access)
114
	}
115
116
	res.GenerateSuccess("Success register an access", *access, 1)
117
	return res
118
}
119
120
// CheckAccess verifies if a given user as the right models.Permission to perform an action on a deck
121
func CheckAccess(userID, deckID uint, perm models.AccessPermission) *models.ResponseHTTP {
122
	db := database.DBConn // DB Conn
123
124
	access := new(models.Access)
125
	res := new(models.ResponseHTTP)
126
127
	if err := db.Joins("User").Joins("Deck").Where("accesses.user_id = ? AND accesses.deck_id = ?", userID, deckID).First(&access).Error; err != nil {
128
		access.Permission = models.AccessNone
129
	}
130
131
	if access.Permission < perm {
132
		res.GenerateError(utils.ErrorForbidden)
133
		return res
134
	}
135
136
	res.GenerateSuccess("Success checking access permissions", *access, 1)
137
	return res
138
}
139
140
// CheckCardLimit verifies that a deck can handle more cards
141
func CheckCardLimit(permission models.Permission, deckID uint) bool {
142
	db := database.DBConn // DB Conn
143
	var count int64
144
145
	if err := db.Table("cards").Where("cards.deck_id = ? AND cards.deleted_at IS NULL", deckID).Count(&count).Error; err != nil {
146
		//TODO: Handle error
147
		return true
148
	}
149
150
	if permission < models.PermMod && count >= utils.MaxCardDeck {
151
		return false
152
	}
153
154
	return true
155
}
156
157
// CheckCode prevents deck code from being duplicated
158
func CheckCode(key, code string) bool {
159
	db := database.DBConn // DB Conn
160
	var count int64
161
162
	if err := db.Table("decks").Where("decks.key = ? AND decks.code = ? AND decks.deleted_at IS NULL", key, code).Count(&count).Error; err != nil {
163
		// TODO: Handle error
164
		return true
165
	}
166
167
	if count != 0 {
168
		return false
169
	}
170
171
	return true
172
}
173
174
// CheckDeckLimit verifies that the user hasn't reached the limit
175
func CheckDeckLimit(user *models.User) bool {
176
	db := database.DBConn // DB Conn
177
	var count int64
178
179
	if err := db.Table("accesses").Where("accesses.user_id = ? AND accesses.permission = ? AND accesses.deleted_at IS NULL", user.ID, models.AccessOwner).Count(&count).Error; err != nil {
180
		//TODO: Handle error
181
		return true
182
	}
183
184
	if user.Permissions < models.PermMod && count >= utils.MaxDeckNormalUser {
185
		return false
186
	}
187
188
	return true
189
}
190
191
// PostSelfEvaluatedMem updates Mem & MemDate
192
func PostSelfEvaluatedMem(user *models.User, card *models.Card, quality uint, training bool) *models.ResponseHTTP {
193
	db := database.DBConn // DB Conn
194
	res := new(models.ResponseHTTP)
195
196
	memDate := new(models.MemDate)
197
198
	if err := db.Joins("Card").Joins("User").Joins("Deck").Where("mem_dates.user_id = ? AND mem_dates.card_id = ?",
199
		user.ID, card.ID).First(&memDate).Error; err != nil {
200
		res.GenerateError(utils.ErrorRequestFailed) // MemDate not found
201
		// TODO: Create a default MemDate
202
		return res
203
	}
204
205
	exMem := FetchMem(memDate.CardID, user.ID)
206
	if exMem.Efactor == 0 {
207
		exMem.FillDefaultValues(user.ID, card.ID)
208
	}
209
210
	core.UpdateMemSelfEvaluated(exMem, training, quality)
211
212
	res.GenerateSuccess("Success Post Mem", nil, 0)
213
	return res
214
}
215
216
// PostMem updates MemDate & Mem
217
func PostMem(user *models.User, card *models.Card, validation *models.CardResponseValidation, training bool) *models.ResponseHTTP {
218
	db := database.DBConn // DB Conn
219
	res := new(models.ResponseHTTP)
220
221
	memDate := new(models.MemDate)
222
223
	if err := db.Joins("Card").Joins("User").Joins("Deck").Where("mem_dates.user_id = ? AND mem_dates.card_id = ?",
224
		user.ID, card.ID).First(&memDate).Error; err != nil {
225
		res.GenerateError(utils.ErrorRequestFailed) // MemDate not found
226
		// TODO: Create a default MemDate
227
		return res
228
	}
229
230
	exMem := FetchMem(memDate.CardID, user.ID)
231
	if exMem.Efactor == 0 {
232
		exMem.FillDefaultValues(user.ID, card.ID)
233
	}
234
235
	if training {
236
		core.UpdateMemTraining(exMem, validation.Validate)
237
	} else {
238
		core.UpdateMem(exMem, validation.Validate)
239
	}
240
	res.GenerateSuccess("Success Post Mem", nil, 0)
241
	return res
242
}
243
244
// PopulateMemDate with default value for a given user & deck
245
// This is used on deck sub
246
func PopulateMemDate(user *models.User, deck *models.Deck) *models.ResponseHTTP {
247
	db := database.DBConn // DB Conn
248
	var cards []models.Card
249
	res := new(models.ResponseHTTP)
250
251
	if err := db.Joins("Deck").Where("cards.deck_id = ?", deck.ID).Find(&cards).Error; err != nil {
252
		res.GenerateError(err.Error()) // MemDate not found
253
		return res
254
	}
255
256
	for i := range cards {
257
		_ = GenerateMemDate(user.ID, cards[i].ID, cards[i].DeckID)
258
	}
259
	res.GenerateSuccess("Success generated mem_date", nil, 0)
260
	return res
261
}
262
263
// GetSubUsers returns a list of users sub to a deck
264
func GetSubUsers(deckID uint) *models.ResponseHTTP {
265
	res := new(models.ResponseHTTP)
266
267
	db := database.DBConn // DB Conn
268
	var users []models.User
269
270
	if err := db.Joins("left join accesses ON users.id = accesses.user_id AND accesses.deck_id = ?", deckID).Where("accesses.permission > ?", models.AccessNone).Find(&users).Error; err != nil {
271
		res.GenerateError(err.Error())
272
		return res
273
	}
274
	res.GenerateSuccess("Success getting sub users", users, len(users))
275
	return res
276
}
277
278
// GenerateMemDate with default nextDate
279
func GenerateMemDate(userID, cardID, deckID uint) *models.ResponseHTTP {
280
	db := database.DBConn // DB Conn
281
	res := new(models.ResponseHTTP)
282
283
	memDate := new(models.MemDate)
284
285
	if err := db.Joins("User").Joins("Card").Where("mem_dates.user_id = ? AND mem_dates.card_id = ?", userID, cardID).First(&memDate).Error; err != nil {
286
		if errors.Is(err, gorm.ErrRecordNotFound) {
287
			memDate.SetDefaultNextDate(userID, cardID, deckID)
288
			db.Create(memDate)
289
		} else {
290
			res.GenerateError(err.Error())
291
			return res
292
		}
293
	}
294
	res.GenerateSuccess("Success generate MemDate", memDate, 1)
295
	return res
296
}
297
298
// FetchMem returns last mem of an user on a given card
299
func FetchMem(cardID, userID uint) *models.Mem {
300
	db := database.DBConn // DB Conn
301
302
	mem := new(models.Mem)
303
	if err := db.Joins("Card").Where("mems.card_id = ? AND mems.user_id = ?", cardID, userID).Order("id desc").First(&mem).Error; err != nil {
304
		if errors.Is(err, gorm.ErrRecordNotFound) {
305
			mem.Efactor = 0
306
		}
307
	}
308
	return mem
309
}
310
311
// GenerateMCQ returns a list of answer
312
func GenerateMCQ(memDate *models.MemDate, userID uint) []string {
313
	mem := FetchMem(memDate.CardID, userID)
314
315
	answersList := make([]string, 4)
316
	if mem.IsMCQ() || memDate.Card.Type == models.CardMCQ {
317
		answersList = memDate.Card.GetMCQAnswers()
318
		if len(answersList) == 4 {
319
			memDate.Card.Type = models.CardMCQ // MCQ
320
		}
321
322
		return answersList
323
	}
324
325
	return answersList
326
}
327
328
// FetchTrainingCards returns training cards
329
func FetchTrainingCards(userID, deckID uint) *models.ResponseHTTP {
330
	res := new(models.ResponseHTTP)
331
	db := database.DBConn // DB Conn
332
333
	var memDates []models.MemDate
334
335
	if err := db.Joins("Deck").Joins("Card").Where("mem_dates.deck_id = ? AND mem_dates.user_id = ?", deckID, userID).Find(&memDates).Error; err != nil {
336
		res.GenerateError(err.Error())
337
		return res
338
	}
339
	responseCard := new(models.ResponseCard)
340
	var answersList []string
341
342
	result := make([]models.ResponseCard, len(memDates))
343
344
	for i := range memDates {
345
		answersList = GenerateMCQ(&memDates[i], userID)
346
		responseCard.Set(&memDates[i], answersList)
347
		result[i] = *responseCard
348
	}
349
350
	rand.Seed(time.Now().UnixNano())
351
	rand.Shuffle(len(result), func(i, j int) { result[i], result[j] = result[j], result[i] })
352
353
	res.GenerateSuccess("Success getting next card", result, len(result))
354
	return res
355
}
356
357
// FetchTodayCard return today cards
358
func FetchTodayCard(userID uint) *models.ResponseHTTP {
359
	db := database.DBConn // DB Conn
360
	t := time.Now()
361
362
	res := new(models.ResponseHTTP)
363
	var memDates []models.MemDate
364
365
	if err := db.Joins(
366
		"left join accesses ON mem_dates.deck_id = accesses.deck_id AND accesses.user_id = ?",
367
		userID).Joins("Card").Joins("Deck").Where("mem_dates.user_id = ? AND mem_dates.next_date < ? AND accesses.permission >= ? AND accesses.toggle_today IS true",
368
		userID, t.AddDate(0, 0, 1).Add(
369
			time.Duration(-t.Hour())*time.Hour), models.AccessStudent).Order("next_date asc").Find(&memDates).Error; err != nil {
370
		res.GenerateError("Today's memDate not found")
371
		return res
372
	}
373
374
	m := make(map[uint][]models.ResponseCard)
375
	wg := new(sync.WaitGroup)
376
	responseCard := new(models.ResponseCard)
377
378
	workers := 10
379
380
	if len(memDates) < 10 {
381
		workers = 1
382
	}
383
384
	M := len(memDates) / workers
385
386
	wg.Add(workers)
387
388
	ch := make(chan models.ResponseCard, len(memDates))
389
390
	for i := 0; i < workers; i++ {
391
		hi, lo := i*M, (i+1)*M
392
		if i == workers-1 {
393
			lo = len(memDates)
394
		}
395
396
		subMemDates := memDates[hi:lo]
397
		go func() {
398
			for index := range subMemDates {
399
				answersList := GenerateMCQ(&subMemDates[index], userID)
400
				responseCard.Set(&subMemDates[index], answersList)
401
				ch <- *responseCard
402
			}
403
			wg.Done()
404
		}()
405
	}
406
	wg.Wait()
407
	close(ch)
408
409
	for toto := range ch {
410
		m[toto.Card.DeckID] = append(m[toto.Card.DeckID], toto)
411
	}
412
413
	todayResponse := new(models.TodayResponse)
414
415
	for key := range m {
416
		deckResponse := models.DeckResponse{
417
			DeckID: key,
418
			Cards:  m[key],
419
			Count:  len(m[key]),
420
		}
421
		todayResponse.DecksReponses = append(todayResponse.DecksReponses, deckResponse)
422
	}
423
424
	sort.Slice(todayResponse.DecksReponses, func(i, j int) bool {
425
		return todayResponse.DecksReponses[i].Count < todayResponse.DecksReponses[j].Count
426
	})
427
428
	todayResponse.Count = len(todayResponse.DecksReponses)
429
430
	res.GenerateSuccess("Success getting next today's cards", todayResponse, len(memDates))
431
	return res
432
}
433