f
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Deploy Admin Panel / Deploy Admin Panel (push) Waiting to run
Deploy Admin Panel / Admin Panel Verification (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Deploy Admin Panel / Deploy Admin Panel (push) Waiting to run
Deploy Admin Panel / Admin Panel Verification (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
This commit is contained in:
parent
9c132174c5
commit
1ab5071345
61 changed files with 2701 additions and 2073 deletions
|
|
@ -53,4 +53,5 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
)
|
)
|
||||||
Button.displayName = "Button"
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants }
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable react-refresh/only-export-components */
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { adminApiClient } from './client'
|
import { adminApiClient } from './client'
|
||||||
import type { AuthResponse, RequestCodeResponse, CodeStatusResponse } from '@/types/models'
|
import type { AuthResponse, RequestCodeResponse, CodeStatusResponse } from '@/types/models'
|
||||||
|
import type { AxiosError } from 'axios'
|
||||||
|
|
||||||
export const authApi = {
|
export const authApi = {
|
||||||
// Request authentication code (creates web code)
|
// Request authentication code (creates web code)
|
||||||
|
|
@ -13,9 +14,10 @@ export const authApi = {
|
||||||
try {
|
try {
|
||||||
const response = await adminApiClient.get(`/api/v2/admin/auth/code-status/${code}`)
|
const response = await adminApiClient.get(`/api/v2/admin/auth/code-status/${code}`)
|
||||||
return response.data
|
return response.data
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
|
const axiosError = error as AxiosError
|
||||||
// If 404, return a proper error response
|
// If 404, return a proper error response
|
||||||
if (error.response?.status === 404) {
|
if (axiosError.response?.status === 404) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
code: code,
|
code: code,
|
||||||
|
|
@ -40,7 +42,7 @@ export const authApi = {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Get current admin info (for token validation)
|
// Get current admin info (for token validation)
|
||||||
getCurrentUser: async (): Promise<{ success: boolean; user?: any; message?: string }> => {
|
getCurrentUser: async (): Promise<{ success: boolean; user?: unknown; message?: string }> => {
|
||||||
const response = await adminApiClient.get('/api/v2/admin/auth/me')
|
const response = await adminApiClient.get('/api/v2/admin/auth/me')
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ describe('API Clients', () => {
|
||||||
const token = 'test-admin-token'
|
const token = 'test-admin-token'
|
||||||
localStorage.setItem('admin_token', token)
|
localStorage.setItem('admin_token', token)
|
||||||
|
|
||||||
const requestConfig = { headers: {} }
|
const requestConfig = { headers: {} as Record<string, string> }
|
||||||
const addAuthToken = (config: any) => {
|
const addAuthToken = (config: { headers: Record<string, string> }) => {
|
||||||
const storedToken = localStorage.getItem('admin_token')
|
const storedToken = localStorage.getItem('admin_token')
|
||||||
if (storedToken) {
|
if (storedToken) {
|
||||||
config.headers.Authorization = `Bearer ${storedToken}`
|
config.headers.Authorization = `Bearer ${storedToken}`
|
||||||
|
|
@ -58,8 +58,8 @@ describe('API Clients', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not add authorization header when no token exists', () => {
|
it('should not add authorization header when no token exists', () => {
|
||||||
const requestConfig = { headers: {} }
|
const requestConfig = { headers: {} as Record<string, string> }
|
||||||
const addAuthToken = (config: any) => {
|
const addAuthToken = (config: { headers: Record<string, string> }) => {
|
||||||
const storedToken = localStorage.getItem('admin_token')
|
const storedToken = localStorage.getItem('admin_token')
|
||||||
if (storedToken) {
|
if (storedToken) {
|
||||||
config.headers.Authorization = `Bearer ${storedToken}`
|
config.headers.Authorization = `Bearer ${storedToken}`
|
||||||
|
|
@ -81,7 +81,7 @@ describe('API Clients', () => {
|
||||||
writable: true,
|
writable: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleAuthError = (error: any) => {
|
const handleAuthError = (error: { response?: { status?: number } }) => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
localStorage.removeItem('admin_token')
|
localStorage.removeItem('admin_token')
|
||||||
window.location.href = '/login'
|
window.location.href = '/login'
|
||||||
|
|
@ -108,7 +108,7 @@ describe('API Clients', () => {
|
||||||
writable: true,
|
writable: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleAuthError = (error: any) => {
|
const handleAuthError = (error: { response?: { status?: number } }) => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
localStorage.removeItem('admin_token')
|
localStorage.removeItem('admin_token')
|
||||||
window.location.href = '/login'
|
window.location.href = '/login'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import axios from 'axios'
|
import axios, { type AxiosError, type InternalAxiosRequestConfig } from 'axios'
|
||||||
import { useAuthStore } from '@/stores/authStore'
|
import { useAuthStore } from '@/stores/authStore'
|
||||||
|
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://api.mnemo-cards.online'
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://api.mnemo-cards.online'
|
||||||
|
|
@ -23,7 +23,7 @@ export const adminApiClient = axios.create({
|
||||||
})
|
})
|
||||||
|
|
||||||
// Request interceptor to add auth token
|
// Request interceptor to add auth token
|
||||||
const addAuthToken = (config: any) => {
|
const addAuthToken = (config: InternalAxiosRequestConfig) => {
|
||||||
const token = localStorage.getItem('admin_token')
|
const token = localStorage.getItem('admin_token')
|
||||||
const isPublicAuthEndpoint =
|
const isPublicAuthEndpoint =
|
||||||
config.url?.includes('/admin/auth/request-code') ||
|
config.url?.includes('/admin/auth/request-code') ||
|
||||||
|
|
@ -33,7 +33,7 @@ const addAuthToken = (config: any) => {
|
||||||
if (token && token.trim()) {
|
if (token && token.trim()) {
|
||||||
// Ensure headers object exists
|
// Ensure headers object exists
|
||||||
if (!config.headers) {
|
if (!config.headers) {
|
||||||
config.headers = {}
|
config.headers = {} as InternalAxiosRequestConfig['headers']
|
||||||
}
|
}
|
||||||
const trimmedToken = token.trim()
|
const trimmedToken = token.trim()
|
||||||
// Set Authorization header - axios will normalize it to lowercase for HTTP
|
// Set Authorization header - axios will normalize it to lowercase for HTTP
|
||||||
|
|
@ -61,14 +61,15 @@ const addAuthToken = (config: any) => {
|
||||||
let isLoggingOut = false
|
let isLoggingOut = false
|
||||||
|
|
||||||
// Response interceptor for error handling
|
// Response interceptor for error handling
|
||||||
const handleAuthError = (error: any) => {
|
const handleAuthError = (error: unknown) => {
|
||||||
if (error.response?.status === 401) {
|
const axiosError = error as AxiosError
|
||||||
|
if (axiosError.response?.status === 401) {
|
||||||
// Check if we're already on login page
|
// Check if we're already on login page
|
||||||
const currentPath = window.location.pathname
|
const currentPath = window.location.pathname
|
||||||
const isPublicAuthEndpoint =
|
const isPublicAuthEndpoint =
|
||||||
error.config?.url?.includes('/admin/auth/request-code') ||
|
axiosError.config?.url?.includes('/admin/auth/request-code') ||
|
||||||
error.config?.url?.includes('/admin/auth/verify-code') ||
|
axiosError.config?.url?.includes('/admin/auth/verify-code') ||
|
||||||
error.config?.url?.includes('/admin/auth/code-status')
|
axiosError.config?.url?.includes('/admin/auth/code-status')
|
||||||
|
|
||||||
// Don't logout for public auth endpoints or if already on login page
|
// Don't logout for public auth endpoints or if already on login page
|
||||||
if (currentPath === '/login' || isPublicAuthEndpoint) {
|
if (currentPath === '/login' || isPublicAuthEndpoint) {
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,5 @@ function Badge({ className, variant, ...props }: BadgeProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export { Badge, badgeVariants }
|
export { Badge, badgeVariants }
|
||||||
|
|
|
||||||
|
|
@ -55,4 +55,5 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
)
|
)
|
||||||
Button.displayName = "Button"
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants }
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
export interface InputProps
|
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
export interface TextareaProps
|
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
|
||||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
|
||||||
|
|
||||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
({ className, ...props }, ref) => {
|
({ className, ...props }, ref) => {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { cardsApi } from '@/api/cards'
|
import { cardsApi } from '@/api/cards'
|
||||||
import type { GameCardDto, PaginatedResponse } from '@/types/models'
|
import type { GameCardDto, PaginatedResponse } from '@/types/models'
|
||||||
|
import type { AxiosError } from 'axios'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
|
@ -71,8 +72,9 @@ export default function CardsPage() {
|
||||||
toast.success('Card created successfully')
|
toast.success('Card created successfully')
|
||||||
closeDialog()
|
closeDialog()
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: unknown) => {
|
||||||
toast.error(error.response?.data?.message || 'Failed to create card')
|
const axiosError = error as AxiosError<{ message?: string }>
|
||||||
|
toast.error(axiosError.response?.data?.message || 'Failed to create card')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -83,8 +85,9 @@ export default function CardsPage() {
|
||||||
toast.success('Card updated successfully')
|
toast.success('Card updated successfully')
|
||||||
closeDialog()
|
closeDialog()
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: unknown) => {
|
||||||
toast.error(error.response?.data?.message || 'Failed to update card')
|
const axiosError = error as AxiosError<{ message?: string }>
|
||||||
|
toast.error(axiosError.response?.data?.message || 'Failed to update card')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -96,8 +99,9 @@ export default function CardsPage() {
|
||||||
setIsDeleteDialogOpen(false)
|
setIsDeleteDialogOpen(false)
|
||||||
setCardToDelete(null)
|
setCardToDelete(null)
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: unknown) => {
|
||||||
toast.error(error.response?.data?.message || 'Failed to delete card')
|
const axiosError = error as AxiosError<{ message?: string }>
|
||||||
|
toast.error(axiosError.response?.data?.message || 'Failed to delete card')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { packsApi } from '@/api/packs'
|
import { packsApi } from '@/api/packs'
|
||||||
import type { EditCardPackDto, CardPackPreviewDto, PaginatedResponse } from '@/types/models'
|
import type { EditCardPackDto, CardPackPreviewDto, PaginatedResponse } from '@/types/models'
|
||||||
|
import type { AxiosError } from 'axios'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
|
@ -77,8 +78,9 @@ export default function PacksPage() {
|
||||||
toast.success('Pack created successfully')
|
toast.success('Pack created successfully')
|
||||||
closeDialog()
|
closeDialog()
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: unknown) => {
|
||||||
toast.error(error.response?.data?.message || 'Failed to create pack')
|
const axiosError = error as AxiosError<{ message?: string }>
|
||||||
|
toast.error(axiosError.response?.data?.message || 'Failed to create pack')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -89,8 +91,9 @@ export default function PacksPage() {
|
||||||
toast.success('Pack updated successfully')
|
toast.success('Pack updated successfully')
|
||||||
closeDialog()
|
closeDialog()
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: unknown) => {
|
||||||
toast.error(error.response?.data?.message || 'Failed to update pack')
|
const axiosError = error as AxiosError<{ message?: string }>
|
||||||
|
toast.error(axiosError.response?.data?.message || 'Failed to update pack')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -102,8 +105,9 @@ export default function PacksPage() {
|
||||||
setIsDeleteDialogOpen(false)
|
setIsDeleteDialogOpen(false)
|
||||||
setPackToDelete(null)
|
setPackToDelete(null)
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: unknown) => {
|
||||||
toast.error(error.response?.data?.message || 'Failed to delete pack')
|
const axiosError = error as AxiosError<{ message?: string }>
|
||||||
|
toast.error(axiosError.response?.data?.message || 'Failed to delete pack')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -141,7 +145,7 @@ export default function PacksPage() {
|
||||||
order: fullPack.order || 0,
|
order: fullPack.order || 0,
|
||||||
})
|
})
|
||||||
setIsDialogOpen(true)
|
setIsDialogOpen(true)
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error('Failed to load pack details')
|
toast.error('Failed to load pack details')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { usersApi } from '@/api/users'
|
import { usersApi } from '@/api/users'
|
||||||
import type { UserDto, PaginatedResponse, PaymentDto } from '@/types/models'
|
import type { UserDto, PaginatedResponse, PaymentDto } from '@/types/models'
|
||||||
|
import type { AxiosError } from 'axios'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
|
@ -71,8 +72,9 @@ export default function UsersPage() {
|
||||||
toast.success('User updated successfully')
|
toast.success('User updated successfully')
|
||||||
closeEditDialog()
|
closeEditDialog()
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: unknown) => {
|
||||||
toast.error(error.response?.data?.message || 'Failed to update user')
|
const axiosError = error as AxiosError<{ message?: string }>
|
||||||
|
toast.error(axiosError.response?.data?.message || 'Failed to update user')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -84,8 +86,9 @@ export default function UsersPage() {
|
||||||
setIsDeleteDialogOpen(false)
|
setIsDeleteDialogOpen(false)
|
||||||
setUserToDelete(null)
|
setUserToDelete(null)
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: unknown) => {
|
||||||
toast.error(error.response?.data?.message || 'Failed to delete user')
|
const axiosError = error as AxiosError<{ message?: string }>
|
||||||
|
toast.error(axiosError.response?.data?.message || 'Failed to delete user')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -112,7 +115,7 @@ export default function UsersPage() {
|
||||||
setUserPurchases(purchases)
|
setUserPurchases(purchases)
|
||||||
setSelectedUser(user)
|
setSelectedUser(user)
|
||||||
setIsPurchasesDialogOpen(true)
|
setIsPurchasesDialogOpen(true)
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error('Failed to load user purchases')
|
toast.error('Failed to load user purchases')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,8 @@ export interface UserDto {
|
||||||
purchases: string[]
|
purchases: string[]
|
||||||
subscription?: boolean
|
subscription?: boolean
|
||||||
subscriptionFeatures: string[]
|
subscriptionFeatures: string[]
|
||||||
userDataDto?: any
|
userDataDto?: unknown
|
||||||
userSettingsDto?: any
|
userSettingsDto?: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentDto {
|
export interface PaymentDto {
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
import 'package:drift/drift.dart' as drift;
|
import 'package:drift/drift.dart' as drift;
|
||||||
|
|
@ -32,7 +33,7 @@ extension PaymentToDto on Payment {
|
||||||
),
|
),
|
||||||
externalToken: externalToken,
|
externalToken: externalToken,
|
||||||
meta: meta,
|
meta: meta,
|
||||||
date: date,
|
date: date.dateTime,
|
||||||
products: productsList,
|
products: productsList,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +53,7 @@ extension PaymentFromDto on PaymentDto {
|
||||||
paymentSystem: paymentSystem.name,
|
paymentSystem: paymentSystem.name,
|
||||||
externalToken: drift.Value(externalToken),
|
externalToken: drift.Value(externalToken),
|
||||||
meta: drift.Value(meta),
|
meta: drift.Value(meta),
|
||||||
date: drift.Value(date),
|
date: drift.Value(PgDateTime(date)),
|
||||||
products: drift.Value(productsJson),
|
products: drift.Value(productsJson),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +66,7 @@ extension PaymentUpdate on PaymentDto {
|
||||||
status: drift.Value(status.name),
|
status: drift.Value(status.name),
|
||||||
externalToken: drift.Value(externalToken),
|
externalToken: drift.Value(externalToken),
|
||||||
meta: drift.Value(meta),
|
meta: drift.Value(meta),
|
||||||
updatedAt: drift.Value(DateTime.now()),
|
updatedAt: drift.Value(PgDateTime(DateTime.now())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/api/purchase/google_play_purchase_handler.dart';
|
import 'package:mnemo_cards_backend/api/purchase/google_play_purchase_handler.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
|
|
@ -69,8 +70,8 @@ class PaymentManager {
|
||||||
await _db.subscriptionDao.createUserSubscription(
|
await _db.subscriptionDao.createUserSubscription(
|
||||||
UserSubscriptionsCompanion.insert(
|
UserSubscriptionsCompanion.insert(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
start: now,
|
start: PgDateTime(now),
|
||||||
finish: endDate,
|
finish: PgDateTime(endDate),
|
||||||
features: drift.Value(plan.features as List<dynamic>),
|
features: drift.Value(plan.features as List<dynamic>),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -149,8 +150,8 @@ class PaymentManager {
|
||||||
await _db.subscriptionDao.createUserSubscription(
|
await _db.subscriptionDao.createUserSubscription(
|
||||||
UserSubscriptionsCompanion.insert(
|
UserSubscriptionsCompanion.insert(
|
||||||
userId: payment.userId,
|
userId: payment.userId,
|
||||||
start: now,
|
start: PgDateTime(now),
|
||||||
finish: endDate,
|
finish: PgDateTime(endDate),
|
||||||
features: drift.Value(plan.features),
|
features: drift.Value(plan.features),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
||||||
|
|
@ -25,8 +26,8 @@ class SubscriptionManager {
|
||||||
return SubscriptionDto(
|
return SubscriptionDto(
|
||||||
page: null,
|
page: null,
|
||||||
isActive: subscription != null,
|
isActive: subscription != null,
|
||||||
start: subscription?.start,
|
start: subscription?.start.dateTime,
|
||||||
finish: subscription?.finish,
|
finish: subscription?.finish.dateTime,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,8 +42,8 @@ class SubscriptionManager {
|
||||||
await _db.subscriptionDao.createUserSubscription(
|
await _db.subscriptionDao.createUserSubscription(
|
||||||
UserSubscriptionsCompanion.insert(
|
UserSubscriptionsCompanion.insert(
|
||||||
userId: user.id!,
|
userId: user.id!,
|
||||||
start: now,
|
start: PgDateTime(now),
|
||||||
finish: endDate,
|
finish: PgDateTime(endDate),
|
||||||
features: const Value([]),
|
features: const Value([]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -86,8 +87,8 @@ class SubscriptionManager {
|
||||||
await _db.subscriptionDao.createUserSubscription(
|
await _db.subscriptionDao.createUserSubscription(
|
||||||
UserSubscriptionsCompanion.insert(
|
UserSubscriptionsCompanion.insert(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
start: now,
|
start: PgDateTime(now),
|
||||||
finish: endDate,
|
finish: PgDateTime(endDate),
|
||||||
features: Value(plan.features as List<dynamic>),
|
features: Value(plan.features as List<dynamic>),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ class AdminAnalyticsApiV2 {
|
||||||
'id': u.id,
|
'id': u.id,
|
||||||
'name': u.name,
|
'name': u.name,
|
||||||
'email': u.email,
|
'email': u.email,
|
||||||
'createdAt': u.createdAt.toIso8601String(),
|
'createdAt': u.createdAt.dateTime.toIso8601String(),
|
||||||
})
|
})
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:shelf/shelf.dart';
|
import 'package:shelf/shelf.dart';
|
||||||
import 'package:shelf_router/shelf_router.dart';
|
import 'package:shelf_router/shelf_router.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
|
|
@ -41,7 +42,7 @@ class AdminCardsApiV2 {
|
||||||
'image': card.image,
|
'image': card.image,
|
||||||
'back': card.back,
|
'back': card.back,
|
||||||
'transcription': card.transcription,
|
'transcription': card.transcription,
|
||||||
'createdAt': card.createdAt.toIso8601String(),
|
'createdAt': card.createdAt.dateTime.toIso8601String(),
|
||||||
}).toList(),
|
}).toList(),
|
||||||
'total': total,
|
'total': total,
|
||||||
}),
|
}),
|
||||||
|
|
@ -83,8 +84,8 @@ class AdminCardsApiV2 {
|
||||||
'image': card.image,
|
'image': card.image,
|
||||||
'back': card.back,
|
'back': card.back,
|
||||||
'transcription': card.transcription,
|
'transcription': card.transcription,
|
||||||
'createdAt': card.createdAt.toIso8601String(),
|
'createdAt': card.createdAt.dateTime.toIso8601String(),
|
||||||
'updatedAt': card.updatedAt?.toIso8601String(),
|
'updatedAt': card.updatedAt?.dateTime.toIso8601String(),
|
||||||
}),
|
}),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
);
|
);
|
||||||
|
|
@ -155,7 +156,7 @@ class AdminCardsApiV2 {
|
||||||
image: data['image'] ?? existing.image,
|
image: data['image'] ?? existing.image,
|
||||||
back: data['back'] ?? existing.back,
|
back: data['back'] ?? existing.back,
|
||||||
transcription: data['transcription'] ?? existing.transcription,
|
transcription: data['transcription'] ?? existing.transcription,
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: PgDateTime(DateTime.now()),
|
||||||
);
|
);
|
||||||
|
|
||||||
await _db.packDao.updateCard(updated);
|
await _db.packDao.updateCard(updated);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
||||||
|
|
@ -245,7 +246,7 @@ class JwtService {
|
||||||
RefreshTokensCompanion.insert(
|
RefreshTokensCompanion.insert(
|
||||||
jti: jti,
|
jti: jti,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
expiresAt: expiresAt, // required field - raw DateTime
|
expiresAt: PgDateTime(expiresAt), // required field - raw DateTime
|
||||||
// createdAt and isBlacklisted use defaults from table
|
// createdAt and isBlacklisted use defaults from table
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -260,7 +261,7 @@ class JwtService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if expired
|
// Check if expired
|
||||||
if (token.expiresAt.isBefore(DateTime.now())) {
|
if (token.expiresAt.dateTime.isBefore(DateTime.now())) {
|
||||||
return true; // Expired tokens are considered invalid
|
return true; // Expired tokens are considered invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import 'dart:developer';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/api/authorize/helpers.dart';
|
import 'package:mnemo_cards_backend/api/authorize/helpers.dart';
|
||||||
import 'package:mnemo_cards_backend/tasks/task_manager.dart';
|
import 'package:mnemo_cards_backend/tasks/task_manager.dart';
|
||||||
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
|
||||||
import 'package:shelf/shelf.dart';
|
import 'package:shelf/shelf.dart';
|
||||||
import 'package:shelf_router/shelf_router.dart';
|
import 'package:shelf_router/shelf_router.dart';
|
||||||
|
|
||||||
|
|
@ -104,9 +103,9 @@ class TasksApiV2 {
|
||||||
'difficulty': t.difficulty,
|
'difficulty': t.difficulty,
|
||||||
'status': t.status,
|
'status': t.status,
|
||||||
'rewards': t.rewards,
|
'rewards': t.rewards,
|
||||||
'createdAt': t.createdAt.toIso8601String(),
|
'createdAt': t.createdAt.dateTime.toIso8601String(),
|
||||||
'expiresAt': t.expiresAt.toIso8601String(),
|
'expiresAt': t.expiresAt.dateTime.toIso8601String(),
|
||||||
'completedAt': t.completedAt?.toIso8601String(),
|
'completedAt': t.completedAt?.dateTime.toIso8601String(),
|
||||||
'proofUrl': t.proofUrl,
|
'proofUrl': t.proofUrl,
|
||||||
'instructions': t.instructions,
|
'instructions': t.instructions,
|
||||||
'tags': t.tags,
|
'tags': t.tags,
|
||||||
|
|
@ -146,9 +145,9 @@ class TasksApiV2 {
|
||||||
'difficulty': task.difficulty,
|
'difficulty': task.difficulty,
|
||||||
'status': task.status,
|
'status': task.status,
|
||||||
'rewards': task.rewards,
|
'rewards': task.rewards,
|
||||||
'createdAt': task.createdAt.toIso8601String(),
|
'createdAt': task.createdAt.dateTime.toIso8601String(),
|
||||||
'expiresAt': task.expiresAt.toIso8601String(),
|
'expiresAt': task.expiresAt.dateTime.toIso8601String(),
|
||||||
'completedAt': task.completedAt?.toIso8601String(),
|
'completedAt': task.completedAt?.dateTime.toIso8601String(),
|
||||||
'proofUrl': task.proofUrl,
|
'proofUrl': task.proofUrl,
|
||||||
'instructions': task.instructions,
|
'instructions': task.instructions,
|
||||||
'tags': task.tags,
|
'tags': task.tags,
|
||||||
|
|
@ -212,8 +211,8 @@ class TasksApiV2 {
|
||||||
'progress': progress.map((p) => {
|
'progress': progress.map((p) => {
|
||||||
'taskId': p.taskId,
|
'taskId': p.taskId,
|
||||||
'progress': p.progress,
|
'progress': p.progress,
|
||||||
'startedAt': p.startedAt.toIso8601String(),
|
'startedAt': p.startedAt.dateTime.toIso8601String(),
|
||||||
'updatedAt': p.updatedAt.toIso8601String(),
|
'updatedAt': p.updatedAt.dateTime.toIso8601String(),
|
||||||
}).toList()
|
}).toList()
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
import 'package:mnemo_cards_backend/main.dart' as backend_main;
|
import 'package:mnemo_cards_backend/main.dart' as backend_main;
|
||||||
|
|
@ -115,8 +116,8 @@ class TelegramBotApiV2 {
|
||||||
final sharesCount = await (_db.selectOnly(_db.shareRequests)
|
final sharesCount = await (_db.selectOnly(_db.shareRequests)
|
||||||
..addColumns([countExpr])
|
..addColumns([countExpr])
|
||||||
..where(_db.shareRequests.telegramUserId.equals(telegramUserId))
|
..where(_db.shareRequests.telegramUserId.equals(telegramUserId))
|
||||||
..where(_db.shareRequests.requestedAt.isBiggerOrEqualValue(todayStart))
|
..where(_db.shareRequests.requestedAt.isBiggerOrEqualValue(PgDateTime(todayStart)))
|
||||||
..where(_db.shareRequests.requestedAt.isSmallerThanValue(todayEnd)))
|
..where(_db.shareRequests.requestedAt.isSmallerThanValue(PgDateTime(todayEnd))))
|
||||||
.map((row) => row.read(countExpr)!)
|
.map((row) => row.read(countExpr)!)
|
||||||
.getSingle();
|
.getSingle();
|
||||||
|
|
||||||
|
|
@ -155,7 +156,7 @@ class TelegramBotApiV2 {
|
||||||
telegramUserId: telegramUserId,
|
telegramUserId: telegramUserId,
|
||||||
telegramUsername: Value(telegramUsername),
|
telegramUsername: Value(telegramUsername),
|
||||||
sharedCardId: Value(sharedCardId),
|
sharedCardId: Value(sharedCardId),
|
||||||
requestedAt: Value(requestedAt),
|
requestedAt: Value(PgDateTime(requestedAt)),
|
||||||
);
|
);
|
||||||
|
|
||||||
await _db.into(_db.shareRequests).insert(companion);
|
await _db.into(_db.shareRequests).insert(companion);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:drift/drift.dart' as drift;
|
import 'package:drift/drift.dart' as drift;
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/api/authorize/helpers.dart';
|
import 'package:mnemo_cards_backend/api/authorize/helpers.dart';
|
||||||
import 'package:mnemo_cards_backend/api/purchase/payment_manager.dart';
|
import 'package:mnemo_cards_backend/api/purchase/payment_manager.dart';
|
||||||
|
|
@ -117,7 +118,7 @@ class UsersApiV2 {
|
||||||
id: drift.Value(user.id!),
|
id: drift.Value(user.id!),
|
||||||
name: name != null ? drift.Value(name) : const drift.Value.absent(),
|
name: name != null ? drift.Value(name) : const drift.Value.absent(),
|
||||||
email: email != null ? drift.Value(email) : const drift.Value.absent(),
|
email: email != null ? drift.Value(email) : const drift.Value.absent(),
|
||||||
updatedAt: drift.Value(DateTime.now()),
|
updatedAt: drift.Value(PgDateTime(DateTime.now())),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -518,8 +519,8 @@ class UsersApiV2 {
|
||||||
totalStudyTimeMinutes: drift.Value(totalStudyTime),
|
totalStudyTimeMinutes: drift.Value(totalStudyTime),
|
||||||
currentStreak: drift.Value(currentStreak),
|
currentStreak: drift.Value(currentStreak),
|
||||||
longestStreak: drift.Value(longestStreak),
|
longestStreak: drift.Value(longestStreak),
|
||||||
lastTimeOnline: drift.Value(now),
|
lastTimeOnline: drift.Value(PgDateTime(now)),
|
||||||
updatedAt: drift.Value(now),
|
updatedAt: drift.Value(PgDateTime(now)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:mnemo_cards_backend/user/admin_ids_service.dart';
|
import 'package:mnemo_cards_backend/user/admin_ids_service.dart';
|
||||||
|
|
||||||
import '../database/database.dart';
|
import '../database/database.dart';
|
||||||
|
|
@ -41,7 +42,7 @@ class CheckAdminsTask with task.Task {
|
||||||
UsersCompanion(
|
UsersCompanion(
|
||||||
id: Value(user.id),
|
id: Value(user.id),
|
||||||
admin: const Value(true),
|
admin: const Value(true),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:mnemo_cards_backend/cron/task.dart' as cron_task;
|
import 'package:mnemo_cards_backend/cron/task.dart' as cron_task;
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
||||||
|
|
||||||
class TasksSeederTask with cron_task.Task {
|
class TasksSeederTask with cron_task.Task {
|
||||||
|
|
@ -19,10 +21,13 @@ class TasksSeederTask with cron_task.Task {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final weekFromNow = now.add(const Duration(days: 7));
|
final weekFromNow = now.add(const Duration(days: 7));
|
||||||
|
|
||||||
final tasks = [
|
// Используем предопределенные UUID для каждой задачи
|
||||||
// App Internal Tasks
|
// Это позволяет избежать дубликатов при повторных запусках
|
||||||
UserTaskModel(
|
final tasksWithIds = [
|
||||||
title: 'Пройди 3 теста сегодня',
|
(
|
||||||
|
id: '00000000-0000-0000-0001-000000000001',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Пройди 3 теста сегодня',
|
||||||
description:
|
description:
|
||||||
'Заверши три тестовые сессии в приложении для улучшения навыков испанского.',
|
'Заверши три тестовые сессии в приложении для улучшения навыков испанского.',
|
||||||
type: 'app_internal',
|
type: 'app_internal',
|
||||||
|
|
@ -36,9 +41,11 @@ class TasksSeederTask with cron_task.Task {
|
||||||
expiresAt: weekFromNow,
|
expiresAt: weekFromNow,
|
||||||
tags: ['tests', 'practice', 'daily'],
|
tags: ['tests', 'practice', 'daily'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
UserTaskModel(
|
(
|
||||||
title: 'Достигни 80% правильных ответов в тесте',
|
id: '00000000-0000-0000-0001-000000000002',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Достигни 80% правильных ответов в тесте',
|
||||||
description:
|
description:
|
||||||
'Пройди тест с результатом не менее 80% правильных ответов.',
|
'Пройди тест с результатом не менее 80% правильных ответов.',
|
||||||
type: 'app_internal',
|
type: 'app_internal',
|
||||||
|
|
@ -52,10 +59,12 @@ class TasksSeederTask with cron_task.Task {
|
||||||
expiresAt: weekFromNow,
|
expiresAt: weekFromNow,
|
||||||
tags: ['tests', 'accuracy', 'challenge'],
|
tags: ['tests', 'accuracy', 'challenge'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
// External Tasks
|
// External Tasks
|
||||||
UserTaskModel(
|
(
|
||||||
title: 'Подпишись на Telegram канал',
|
id: '00000000-0000-0000-0001-000000000003',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Подпишись на Telegram канал',
|
||||||
description:
|
description:
|
||||||
'Подпишись на наш Telegram канал @mnemo_cards для получения обновлений и советов по изучению испанского.',
|
'Подпишись на наш Telegram канал @mnemo_cards для получения обновлений и советов по изучению испанского.',
|
||||||
type: 'external',
|
type: 'external',
|
||||||
|
|
@ -69,9 +78,11 @@ class TasksSeederTask with cron_task.Task {
|
||||||
expiresAt: weekFromNow,
|
expiresAt: weekFromNow,
|
||||||
tags: ['telegram', 'social', 'subscription'],
|
tags: ['telegram', 'social', 'subscription'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
UserTaskModel(
|
(
|
||||||
title: 'Закажи еду на испанском',
|
id: '00000000-0000-0000-0001-000000000004',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Закажи еду на испанском',
|
||||||
description:
|
description:
|
||||||
'Сделай заказ в ресторане или кафе, используя испанский язык. Запиши процесс на видео для подтверждения.',
|
'Сделай заказ в ресторане или кафе, используя испанский язык. Запиши процесс на видео для подтверждения.',
|
||||||
type: 'external',
|
type: 'external',
|
||||||
|
|
@ -91,9 +102,11 @@ class TasksSeederTask with cron_task.Task {
|
||||||
'Запиши видео, где ты делаешь заказ на испанском. Убедись, что официант понимает тебя.',
|
'Запиши видео, где ты делаешь заказ на испанском. Убедись, что официант понимает тебя.',
|
||||||
tags: ['speaking', 'restaurant', 'video', 'real_world'],
|
tags: ['speaking', 'restaurant', 'video', 'real_world'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
UserTaskModel(
|
(
|
||||||
title: 'Посмотри испанский фильм с субтитрами',
|
id: '00000000-0000-0000-0001-000000000005',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Посмотри испанский фильм с субтитрами',
|
||||||
description:
|
description:
|
||||||
'Посмотри испанский фильм или сериал с испанскими субтитрами в течение 30 минут.',
|
'Посмотри испанский фильм или сериал с испанскими субтитрами в течение 30 минут.',
|
||||||
type: 'external',
|
type: 'external',
|
||||||
|
|
@ -107,10 +120,12 @@ class TasksSeederTask with cron_task.Task {
|
||||||
expiresAt: weekFromNow,
|
expiresAt: weekFromNow,
|
||||||
tags: ['listening', 'media', 'subtitles'],
|
tags: ['listening', 'media', 'subtitles'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
// Social Tasks
|
// Social Tasks
|
||||||
UserTaskModel(
|
(
|
||||||
title: 'Поделись прогрессом в соцсетях',
|
id: '00000000-0000-0000-0001-000000000006',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Поделись прогрессом в соцсетях',
|
||||||
description:
|
description:
|
||||||
'Поделись своим прогрессом в изучении испанского в социальных сетях с хэштегом #MnemoCards.',
|
'Поделись своим прогрессом в изучении испанского в социальных сетях с хэштегом #MnemoCards.',
|
||||||
type: 'social',
|
type: 'social',
|
||||||
|
|
@ -124,9 +139,11 @@ class TasksSeederTask with cron_task.Task {
|
||||||
expiresAt: weekFromNow,
|
expiresAt: weekFromNow,
|
||||||
tags: ['social', 'sharing', 'progress'],
|
tags: ['social', 'sharing', 'progress'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
UserTaskModel(
|
(
|
||||||
title: 'Найди партнёра для практики',
|
id: '00000000-0000-0000-0001-000000000007',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Найди партнёра для практики',
|
||||||
description:
|
description:
|
||||||
'Найди собеседника для практики испанского языка (друга, коллегу или через языковые приложения).',
|
'Найди собеседника для практики испанского языка (друга, коллегу или через языковые приложения).',
|
||||||
type: 'social',
|
type: 'social',
|
||||||
|
|
@ -140,10 +157,12 @@ class TasksSeederTask with cron_task.Task {
|
||||||
expiresAt: weekFromNow.add(const Duration(days: 7)), // 2 weeks
|
expiresAt: weekFromNow.add(const Duration(days: 7)), // 2 weeks
|
||||||
tags: ['conversation', 'partner', 'practice'],
|
tags: ['conversation', 'partner', 'practice'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
// Additional App Tasks
|
// Additional App Tasks
|
||||||
UserTaskModel(
|
(
|
||||||
title: 'Изучай слова ежедневно 7 дней подряд',
|
id: '00000000-0000-0000-0001-000000000008',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Изучай слова ежедневно 7 дней подряд',
|
||||||
description:
|
description:
|
||||||
'Учи новые слова в приложении каждый день в течение недели.',
|
'Учи новые слова в приложении каждый день в течение недели.',
|
||||||
type: 'app_internal',
|
type: 'app_internal',
|
||||||
|
|
@ -161,9 +180,11 @@ class TasksSeederTask with cron_task.Task {
|
||||||
expiresAt: weekFromNow.add(const Duration(days: 21)), // 4 weeks
|
expiresAt: weekFromNow.add(const Duration(days: 21)), // 4 weeks
|
||||||
tags: ['vocabulary', 'daily', 'streak', 'consistency'],
|
tags: ['vocabulary', 'daily', 'streak', 'consistency'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
UserTaskModel(
|
(
|
||||||
title: 'Пройди тест по грамматике',
|
id: '00000000-0000-0000-0001-000000000009',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Пройди тест по грамматике',
|
||||||
description:
|
description:
|
||||||
'Заверши специализированный тест по грамматике испанского языка.',
|
'Заверши специализированный тест по грамматике испанского языка.',
|
||||||
type: 'app_internal',
|
type: 'app_internal',
|
||||||
|
|
@ -177,9 +198,11 @@ class TasksSeederTask with cron_task.Task {
|
||||||
expiresAt: weekFromNow,
|
expiresAt: weekFromNow,
|
||||||
tags: ['grammar', 'tests', 'knowledge'],
|
tags: ['grammar', 'tests', 'knowledge'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
UserTaskModel(
|
(
|
||||||
title: 'Практикуй произношение',
|
id: '00000000-0000-0000-0001-000000000010',
|
||||||
|
task: UserTaskModel(
|
||||||
|
title: 'Практикуй произношение',
|
||||||
description:
|
description:
|
||||||
'Заверши урок по произношению или запиши себя, читая текст на испанском.',
|
'Заверши урок по произношению или запиши себя, читая текст на испанском.',
|
||||||
type: 'app_internal',
|
type: 'app_internal',
|
||||||
|
|
@ -193,24 +216,28 @@ class TasksSeederTask with cron_task.Task {
|
||||||
expiresAt: weekFromNow,
|
expiresAt: weekFromNow,
|
||||||
tags: ['pronunciation', 'speaking', 'practice'],
|
tags: ['pronunciation', 'speaking', 'practice'],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Convert UserTaskModel to UserTasksCompanion and insert
|
// Convert UserTaskModel to UserTasksCompanion and insert
|
||||||
// Используем InsertMode.insertOrIgnore для автоматического игнорирования дубликатов через ON CONFLICT
|
// PostgreSQL автоматически генерирует UUID через DEFAULT gen_random_uuid()
|
||||||
for (final taskModel in tasks) {
|
// Используем InsertMode.insertOrIgnore с предопределенными UUID для идемпотентности
|
||||||
|
for (final taskData in tasksWithIds) {
|
||||||
|
final taskModel = taskData.task;
|
||||||
final rewardsJson = taskModel.rewards.map((r) => r.toJson()).toList();
|
final rewardsJson = taskModel.rewards.map((r) => r.toJson()).toList();
|
||||||
|
|
||||||
await _db.into(_db.userTasks).insert(
|
await _db.into(_db.userTasks).insert(
|
||||||
UserTasksCompanion.insert(
|
UserTasksCompanion.insert(
|
||||||
|
id: Value(taskData.id), // Предопределенный UUID для идемпотентности seeder'а
|
||||||
title: taskModel.title,
|
title: taskModel.title,
|
||||||
description: taskModel.description,
|
description: taskModel.description,
|
||||||
type: taskModel.type,
|
type: taskModel.type,
|
||||||
difficulty: taskModel.difficulty,
|
difficulty: taskModel.difficulty,
|
||||||
status: taskModel.status,
|
status: taskModel.status,
|
||||||
rewards: Value(rewardsJson),
|
rewards: Value(rewardsJson),
|
||||||
createdAt: Value(taskModel.createdAt),
|
createdAt: Value(PgDateTime(taskModel.createdAt)),
|
||||||
expiresAt: taskModel.expiresAt,
|
expiresAt: PgDateTime(taskModel.expiresAt),
|
||||||
completedAt: Value(taskModel.completedAt),
|
completedAt: Value(taskModel.completedAt?.let(PgDateTime.new)),
|
||||||
proofUrl: Value(taskModel.proofUrl),
|
proofUrl: Value(taskModel.proofUrl),
|
||||||
instructions: Value(taskModel.instructions),
|
instructions: Value(taskModel.instructions),
|
||||||
tags: Value(taskModel.tags),
|
tags: Value(taskModel.tags),
|
||||||
|
|
@ -220,7 +247,7 @@ class TasksSeederTask with cron_task.Task {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
print('Successfully seeded ${tasks.length} user tasks (duplicates ignored)');
|
print('Successfully seeded ${tasksWithIds.length} user tasks (duplicates ignored)');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error seeding tasks: $e');
|
print('Error seeding tasks: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
import '../tables/achievements.dart';
|
import '../tables/achievements.dart';
|
||||||
|
|
||||||
|
|
@ -45,7 +46,7 @@ class AchievementDao extends DatabaseAccessor<AppDatabase> with _$AchievementDao
|
||||||
..where((ua) => ua.userId.equals(userId) & ua.achievementId.equals(achievementId))
|
..where((ua) => ua.userId.equals(userId) & ua.achievementId.equals(achievementId))
|
||||||
).write(UserAchievementsCompanion(
|
).write(UserAchievementsCompanion(
|
||||||
progress: Value(progress),
|
progress: Value(progress),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
return count > 0;
|
return count > 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
import '../tables/discounts.dart';
|
import '../tables/discounts.dart';
|
||||||
import '../tables/users.dart';
|
import '../tables/users.dart';
|
||||||
|
|
@ -18,7 +19,7 @@ class DiscountDao extends DatabaseAccessor<AppDatabase> with _$DiscountDaoMixin
|
||||||
|
|
||||||
/// Получить все активные кампании
|
/// Получить все активные кампании
|
||||||
Future<List<DiscountCampaign>> getActiveCampaigns() {
|
Future<List<DiscountCampaign>> getActiveCampaigns() {
|
||||||
final now = DateTime.now();
|
final now = PgDateTime(DateTime.now());
|
||||||
return (select(db.discountCampaigns)
|
return (select(db.discountCampaigns)
|
||||||
..where((c) => c.status.equals('active'))
|
..where((c) => c.status.equals('active'))
|
||||||
..where((c) => c.start.isSmallerOrEqualValue(now))
|
..where((c) => c.start.isSmallerOrEqualValue(now))
|
||||||
|
|
@ -44,7 +45,7 @@ class DiscountDao extends DatabaseAccessor<AppDatabase> with _$DiscountDaoMixin
|
||||||
..where((c) => c.id.equals(campaignId))
|
..where((c) => c.id.equals(campaignId))
|
||||||
).write(DiscountCampaignsCompanion(
|
).write(DiscountCampaignsCompanion(
|
||||||
status: Value(status),
|
status: Value(status),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +78,7 @@ class DiscountDao extends DatabaseAccessor<AppDatabase> with _$DiscountDaoMixin
|
||||||
..where((c) => c.id.equals(campaignId))
|
..where((c) => c.id.equals(campaignId))
|
||||||
).write(DiscountCampaignsCompanion(
|
).write(DiscountCampaignsCompanion(
|
||||||
isDeleted: const Value(true),
|
isDeleted: const Value(true),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +172,7 @@ class DiscountDao extends DatabaseAccessor<AppDatabase> with _$DiscountDaoMixin
|
||||||
String? productType,
|
String? productType,
|
||||||
String? productId,
|
String? productId,
|
||||||
}) async {
|
}) async {
|
||||||
final now = DateTime.now();
|
final now = PgDateTime(DateTime.now());
|
||||||
final query = select(db.discountCampaigns)
|
final query = select(db.discountCampaigns)
|
||||||
..where((c) => c.status.equals('active'))
|
..where((c) => c.status.equals('active'))
|
||||||
..where((c) => c.start.isSmallerOrEqualValue(now))
|
..where((c) => c.start.isSmallerOrEqualValue(now))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
import '../tables/packs.dart';
|
import '../tables/packs.dart';
|
||||||
import '../tables/relations.dart';
|
import '../tables/relations.dart';
|
||||||
|
|
@ -60,7 +61,7 @@ class PackDao extends DatabaseAccessor<AppDatabase> with _$PackDaoMixin {
|
||||||
if (packId == null) throw ArgumentError('Pack ID is required');
|
if (packId == null) throw ArgumentError('Pack ID is required');
|
||||||
|
|
||||||
return (update(cardPacks)..where((p) => p.id.equals(packId)))
|
return (update(cardPacks)..where((p) => p.id.equals(packId)))
|
||||||
.write(updates.copyWith(updatedAt: Value(DateTime.now())));
|
.write(updates.copyWith(updatedAt: Value(PgDateTime(DateTime.now()))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Удалить пак (soft delete)
|
/// Удалить пак (soft delete)
|
||||||
|
|
@ -68,7 +69,7 @@ class PackDao extends DatabaseAccessor<AppDatabase> with _$PackDaoMixin {
|
||||||
return (update(cardPacks)..where((p) => p.id.equals(packId)))
|
return (update(cardPacks)..where((p) => p.id.equals(packId)))
|
||||||
.write(CardPacksCompanion(
|
.write(CardPacksCompanion(
|
||||||
isDeleted: const Value(true),
|
isDeleted: const Value(true),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,7 +138,7 @@ class PackDao extends DatabaseAccessor<AppDatabase> with _$PackDaoMixin {
|
||||||
return (update(gameCards)..where((c) => c.id.equals(cardId)))
|
return (update(gameCards)..where((c) => c.id.equals(cardId)))
|
||||||
.write(GameCardsCompanion(
|
.write(GameCardsCompanion(
|
||||||
isDeleted: const Value(true),
|
isDeleted: const Value(true),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
import '../tables/payments.dart';
|
import '../tables/payments.dart';
|
||||||
|
|
||||||
|
|
@ -56,7 +57,7 @@ class PaymentDao extends DatabaseAccessor<AppDatabase> with _$PaymentDaoMixin {
|
||||||
'SELECT id FROM payments WHERE user_id = ? AND date = ? AND amount = ? ORDER BY created_at DESC LIMIT 1',
|
'SELECT id FROM payments WHERE user_id = ? AND date = ? AND amount = ? ORDER BY created_at DESC LIMIT 1',
|
||||||
variables: [
|
variables: [
|
||||||
Variable.withString(payment.userId.value),
|
Variable.withString(payment.userId.value),
|
||||||
Variable.withDateTime(inserted.date),
|
Variable.withDateTime(inserted.date.dateTime),
|
||||||
Variable.withString(inserted.amount),
|
Variable.withString(inserted.amount),
|
||||||
],
|
],
|
||||||
readsFrom: {payments},
|
readsFrom: {payments},
|
||||||
|
|
@ -81,7 +82,7 @@ class PaymentDao extends DatabaseAccessor<AppDatabase> with _$PaymentDaoMixin {
|
||||||
return (update(payments)..where((p) => p.id.equals(paymentId)))
|
return (update(payments)..where((p) => p.id.equals(paymentId)))
|
||||||
.write(PaymentsCompanion(
|
.write(PaymentsCompanion(
|
||||||
status: Value(status),
|
status: Value(status),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
import '../tables/promo_codes.dart';
|
import '../tables/promo_codes.dart';
|
||||||
import '../tables/users.dart';
|
import '../tables/users.dart';
|
||||||
|
|
@ -18,7 +19,7 @@ class PromoCodeDao extends DatabaseAccessor<AppDatabase> with _$PromoCodeDaoMixi
|
||||||
|
|
||||||
/// Получить все активные кампании
|
/// Получить все активные кампании
|
||||||
Future<List<PromoCodesCampaign>> getActiveCampaigns() {
|
Future<List<PromoCodesCampaign>> getActiveCampaigns() {
|
||||||
final now = DateTime.now();
|
final now = PgDateTime(DateTime.now());
|
||||||
return (select(db.promoCodesCampaigns)
|
return (select(db.promoCodesCampaigns)
|
||||||
..where((c) => c.status.equals('active'))
|
..where((c) => c.status.equals('active'))
|
||||||
..where((c) => c.start.isSmallerOrEqualValue(now))
|
..where((c) => c.start.isSmallerOrEqualValue(now))
|
||||||
|
|
@ -44,7 +45,7 @@ class PromoCodeDao extends DatabaseAccessor<AppDatabase> with _$PromoCodeDaoMixi
|
||||||
..where((c) => c.id.equals(campaignId))
|
..where((c) => c.id.equals(campaignId))
|
||||||
).write(PromoCodesCampaignsCompanion(
|
).write(PromoCodesCampaignsCompanion(
|
||||||
status: Value(status),
|
status: Value(status),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,7 +110,7 @@ class PromoCodeDao extends DatabaseAccessor<AppDatabase> with _$PromoCodeDaoMixi
|
||||||
if (code != null) {
|
if (code != null) {
|
||||||
await update(db.promoCodes).replace(code.copyWith(
|
await update(db.promoCodes).replace(code.copyWith(
|
||||||
activations: code.activations + 1,
|
activations: code.activations + 1,
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: PgDateTime(DateTime.now()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
import '../tables/statistics.dart';
|
import '../tables/statistics.dart';
|
||||||
import '../tables/users.dart';
|
import '../tables/users.dart';
|
||||||
|
|
@ -42,10 +43,10 @@ class StatisticsDao extends DatabaseAccessor<AppDatabase> with _$StatisticsDaoMi
|
||||||
..orderBy([(s) => OrderingTerm.desc(s.startTime)]);
|
..orderBy([(s) => OrderingTerm.desc(s.startTime)]);
|
||||||
|
|
||||||
if (fromDate != null) {
|
if (fromDate != null) {
|
||||||
query.where((s) => s.startTime.isBiggerOrEqualValue(fromDate));
|
query.where((s) => s.startTime.isBiggerOrEqualValue(PgDateTime(fromDate)));
|
||||||
}
|
}
|
||||||
if (toDate != null) {
|
if (toDate != null) {
|
||||||
query.where((s) => s.startTime.isSmallerOrEqualValue(toDate));
|
query.where((s) => s.startTime.isSmallerOrEqualValue(PgDateTime(toDate)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (limit != null) {
|
if (limit != null) {
|
||||||
|
|
@ -74,8 +75,8 @@ class StatisticsDao extends DatabaseAccessor<AppDatabase> with _$StatisticsDaoMi
|
||||||
}) {
|
}) {
|
||||||
final updates = StudySessionsCompanion(
|
final updates = StudySessionsCompanion(
|
||||||
id: Value(sessionId),
|
id: Value(sessionId),
|
||||||
endTime: Value(DateTime.now()),
|
endTime: Value(PgDateTime(DateTime.now())),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
wordsLearned: wordsLearned != null ? Value(wordsLearned) : const Value.absent(),
|
wordsLearned: wordsLearned != null ? Value(wordsLearned) : const Value.absent(),
|
||||||
testsCompleted: testsCompleted != null ? Value(testsCompleted) : const Value.absent(),
|
testsCompleted: testsCompleted != null ? Value(testsCompleted) : const Value.absent(),
|
||||||
accuracy: accuracy != null ? Value(accuracy) : const Value.absent(),
|
accuracy: accuracy != null ? Value(accuracy) : const Value.absent(),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
import '../tables/subscriptions.dart';
|
import '../tables/subscriptions.dart';
|
||||||
import '../tables/users.dart';
|
import '../tables/users.dart';
|
||||||
|
|
@ -46,7 +47,7 @@ class SubscriptionDao extends DatabaseAccessor<AppDatabase> with _$SubscriptionD
|
||||||
|
|
||||||
/// Получить активную подписку пользователя
|
/// Получить активную подписку пользователя
|
||||||
Future<UserSubscription?> getActiveSubscription(String userId) async {
|
Future<UserSubscription?> getActiveSubscription(String userId) async {
|
||||||
final now = DateTime.now();
|
final now = PgDateTime(DateTime.now());
|
||||||
return (select(userSubscriptions)
|
return (select(userSubscriptions)
|
||||||
..where((us) => us.userId.equals(userId))
|
..where((us) => us.userId.equals(userId))
|
||||||
..where((us) => us.start.isSmallerThanValue(now))
|
..where((us) => us.start.isSmallerThanValue(now))
|
||||||
|
|
@ -81,7 +82,7 @@ class SubscriptionDao extends DatabaseAccessor<AppDatabase> with _$SubscriptionD
|
||||||
|
|
||||||
/// Получить всех пользователей с активными подписками
|
/// Получить всех пользователей с активными подписками
|
||||||
Future<List<UserSubscription>> getActiveSubscriptions() async {
|
Future<List<UserSubscription>> getActiveSubscriptions() async {
|
||||||
final now = DateTime.now();
|
final now = PgDateTime(DateTime.now());
|
||||||
return (select(userSubscriptions)
|
return (select(userSubscriptions)
|
||||||
..where((us) => us.start.isSmallerThanValue(now))
|
..where((us) => us.start.isSmallerThanValue(now))
|
||||||
..where((us) => us.finish.isBiggerThanValue(now))
|
..where((us) => us.finish.isBiggerThanValue(now))
|
||||||
|
|
@ -100,7 +101,7 @@ class SubscriptionDao extends DatabaseAccessor<AppDatabase> with _$SubscriptionD
|
||||||
if (subscription != null) {
|
if (subscription != null) {
|
||||||
await update(userSubscriptions).replace(
|
await update(userSubscriptions).replace(
|
||||||
subscription.copyWith(
|
subscription.copyWith(
|
||||||
finish: DateTime.now(),
|
finish: PgDateTime(DateTime.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
|
|
||||||
part 'task_dao.g.dart';
|
part 'task_dao.g.dart';
|
||||||
|
|
@ -32,10 +33,11 @@ class TaskDao extends DatabaseAccessor<AppDatabase> with _$TaskDaoMixin {
|
||||||
|
|
||||||
/// Обновить время последнего выполнения
|
/// Обновить время последнего выполнения
|
||||||
Future<void> updateLastExecution(String taskId) {
|
Future<void> updateLastExecution(String taskId) {
|
||||||
|
final now = PgDateTime(DateTime.now());
|
||||||
return (update(db.tasks)..where((t) => t.id.equals(taskId)))
|
return (update(db.tasks)..where((t) => t.id.equals(taskId)))
|
||||||
.write(TasksCompanion(
|
.write(TasksCompanion(
|
||||||
lastExecution: Value(DateTime.now()),
|
lastExecution: Value(now),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(now),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,7 +68,7 @@ class TaskDao extends DatabaseAccessor<AppDatabase> with _$TaskDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeOnly) {
|
if (activeOnly) {
|
||||||
final now = DateTime.now();
|
final now = PgDateTime(DateTime.now());
|
||||||
query.where((ut) => ut.expiresAt.isBiggerThanValue(now));
|
query.where((ut) => ut.expiresAt.isBiggerThanValue(now));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,11 +90,12 @@ class TaskDao extends DatabaseAccessor<AppDatabase> with _$TaskDaoMixin {
|
||||||
|
|
||||||
/// Завершить задачу пользователя
|
/// Завершить задачу пользователя
|
||||||
Future<void> completeUserTask(String taskId) {
|
Future<void> completeUserTask(String taskId) {
|
||||||
|
final now = PgDateTime(DateTime.now());
|
||||||
return (update(db.userTasks)..where((ut) => ut.id.equals(taskId)))
|
return (update(db.userTasks)..where((ut) => ut.id.equals(taskId)))
|
||||||
.write(UserTasksCompanion(
|
.write(UserTasksCompanion(
|
||||||
status: const Value('completed'),
|
status: const Value('completed'),
|
||||||
completedAt: Value(DateTime.now()),
|
completedAt: Value(now),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(now),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
import '../tables/tests.dart';
|
import '../tables/tests.dart';
|
||||||
import '../tables/packs.dart';
|
import '../tables/packs.dart';
|
||||||
|
|
@ -52,7 +53,7 @@ class TestDao extends DatabaseAccessor<AppDatabase> with _$TestDaoMixin {
|
||||||
return (update(tests)..where((t) => t.id.equals(testId)))
|
return (update(tests)..where((t) => t.id.equals(testId)))
|
||||||
.write(TestsCompanion(
|
.write(TestsCompanion(
|
||||||
isDeleted: const Value(true),
|
isDeleted: const Value(true),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../database.dart';
|
import '../database.dart';
|
||||||
import '../tables/users.dart';
|
import '../tables/users.dart';
|
||||||
import '../tables/auth.dart';
|
import '../tables/auth.dart';
|
||||||
|
|
@ -82,7 +83,7 @@ class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
|
||||||
return (update(users)..where((u) => u.id.equals(userId)))
|
return (update(users)..where((u) => u.id.equals(userId)))
|
||||||
.write(UsersCompanion(
|
.write(UsersCompanion(
|
||||||
isDeleted: const Value(true),
|
isDeleted: const Value(true),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +116,7 @@ class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
|
||||||
|
|
||||||
/// Получить пользователей с активной подпиской
|
/// Получить пользователей с активной подпиской
|
||||||
Stream<List<User>> watchUsersWithActiveSubscription() {
|
Stream<List<User>> watchUsersWithActiveSubscription() {
|
||||||
final now = DateTime.now();
|
final now = PgDateTime(DateTime.now());
|
||||||
final query = select(users).join([
|
final query = select(users).join([
|
||||||
innerJoin(
|
innerJoin(
|
||||||
db.userSubscriptions,
|
db.userSubscriptions,
|
||||||
|
|
@ -155,15 +156,16 @@ class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
|
||||||
if (userId == null) throw ArgumentError('User ID is required');
|
if (userId == null) throw ArgumentError('User ID is required');
|
||||||
|
|
||||||
return (update(userDatas)..where((ud) => ud.userId.equals(userId)))
|
return (update(userDatas)..where((ud) => ud.userId.equals(userId)))
|
||||||
.write(updates.copyWith(updatedAt: Value(DateTime.now())));
|
.write(updates.copyWith(updatedAt: Value(PgDateTime(DateTime.now()))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Обновить время последнего визита
|
/// Обновить время последнего визита
|
||||||
Future<void> updateLastOnline(String userId) async {
|
Future<void> updateLastOnline(String userId) async {
|
||||||
|
final now = PgDateTime(DateTime.now());
|
||||||
await (update(userDatas)..where((ud) => ud.userId.equals(userId)))
|
await (update(userDatas)..where((ud) => ud.userId.equals(userId)))
|
||||||
.write(UserDatasCompanion(
|
.write(UserDatasCompanion(
|
||||||
lastTimeOnline: Value(DateTime.now()),
|
lastTimeOnline: Value(now),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(now),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,7 +190,7 @@ class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
|
||||||
Future<Token?> getTokenByUserId(String userId) {
|
Future<Token?> getTokenByUserId(String userId) {
|
||||||
return (select(tokens)
|
return (select(tokens)
|
||||||
..where((t) => t.userId.equals(userId))
|
..where((t) => t.userId.equals(userId))
|
||||||
..where((t) => t.expires.isBiggerThanValue(DateTime.now()))
|
..where((t) => t.expires.isBiggerThanValue(PgDateTime(DateTime.now())))
|
||||||
..orderBy([(t) => OrderingTerm.desc(t.created)])
|
..orderBy([(t) => OrderingTerm.desc(t.created)])
|
||||||
).getSingleOrNull();
|
).getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
@ -212,7 +214,7 @@ class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
|
||||||
/// Удалить истекшие токены
|
/// Удалить истекшие токены
|
||||||
Future<int> deleteExpiredTokens() {
|
Future<int> deleteExpiredTokens() {
|
||||||
return (delete(tokens)
|
return (delete(tokens)
|
||||||
..where((t) => t.expires.isSmallerThanValue(DateTime.now()))
|
..where((t) => t.expires.isSmallerThanValue(PgDateTime(DateTime.now())))
|
||||||
).go();
|
).go();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +231,7 @@ class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
|
||||||
return (select(refreshTokens)
|
return (select(refreshTokens)
|
||||||
..where((rt) => rt.userId.equals(userId))
|
..where((rt) => rt.userId.equals(userId))
|
||||||
..where((rt) => rt.isBlacklisted.equals(false))
|
..where((rt) => rt.isBlacklisted.equals(false))
|
||||||
..where((rt) => rt.expiresAt.isBiggerThanValue(DateTime.now()))
|
..where((rt) => rt.expiresAt.isBiggerThanValue(PgDateTime(DateTime.now())))
|
||||||
..orderBy([(rt) => OrderingTerm.desc(rt.createdAt)])
|
..orderBy([(rt) => OrderingTerm.desc(rt.createdAt)])
|
||||||
).get();
|
).get();
|
||||||
}
|
}
|
||||||
|
|
@ -259,7 +261,7 @@ class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
|
||||||
/// Удалить истекшие refresh токены
|
/// Удалить истекшие refresh токены
|
||||||
Future<int> deleteExpiredRefreshTokens() {
|
Future<int> deleteExpiredRefreshTokens() {
|
||||||
return (delete(refreshTokens)
|
return (delete(refreshTokens)
|
||||||
..where((rt) => rt.expiresAt.isSmallerThanValue(DateTime.now()))
|
..where((rt) => rt.expiresAt.isSmallerThanValue(PgDateTime(DateTime.now())))
|
||||||
).go();
|
).go();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,14 +284,14 @@ class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
|
||||||
return (update(db.telegramAuthCodes)..where((ac) => ac.code.equals(code)))
|
return (update(db.telegramAuthCodes)..where((ac) => ac.code.equals(code)))
|
||||||
.write(TelegramAuthCodesCompanion(
|
.write(TelegramAuthCodesCompanion(
|
||||||
isUsed: const Value(true),
|
isUsed: const Value(true),
|
||||||
usedAt: Value(DateTime.now()),
|
usedAt: Value(PgDateTime(DateTime.now())),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Удалить истекшие коды
|
/// Удалить истекшие коды
|
||||||
Future<int> deleteExpiredAuthCodes() {
|
Future<int> deleteExpiredAuthCodes() {
|
||||||
return (delete(db.telegramAuthCodes)
|
return (delete(db.telegramAuthCodes)
|
||||||
..where((ac) => ac.expiresAt.isSmallerThanValue(DateTime.now()))
|
..where((ac) => ac.expiresAt.isSmallerThanValue(PgDateTime(DateTime.now())))
|
||||||
).go();
|
).go();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ import 'daos/promo_code_dao.dart';
|
||||||
import 'daos/discount_dao.dart';
|
import 'daos/discount_dao.dart';
|
||||||
import 'daos/statistics_dao.dart';
|
import 'daos/statistics_dao.dart';
|
||||||
import 'daos/achievement_dao.dart';
|
import 'daos/achievement_dao.dart';
|
||||||
import 'postgres_constants.dart';
|
|
||||||
|
|
||||||
// Сгенерированный код будет здесь
|
// Сгенерированный код будет здесь
|
||||||
part 'database.g.dart';
|
part 'database.g.dart';
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
/// PostgreSQL-совместимое выражение для текущей даты и времени как Unix timestamp (bigint)
|
|
||||||
/// Используется вместо currentDateAndTime, который использует SQLite-специфичную функцию strftime
|
|
||||||
/// Drift хранит DateTime как bigint (Unix timestamp в секундах), поэтому используем EXTRACT(EPOCH FROM NOW())::BIGINT
|
|
||||||
const Expression<DateTime> currentTimestamp = CustomExpression<DateTime>('EXTRACT(EPOCH FROM NOW())::BIGINT');
|
|
||||||
|
|
@ -1,25 +1,31 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import '../postgres_constants.dart';
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'users.dart';
|
import 'users.dart';
|
||||||
|
|
||||||
/// Таблица UserAchievements - достижения пользователей
|
/// Таблица UserAchievements - достижения пользователей
|
||||||
class UserAchievements extends Table {
|
class UserAchievements extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
TextColumn get userId => text().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get userId => text()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
// Achievement ID (from AchievementDefinitions)
|
// Achievement ID (from AchievementDefinitions)
|
||||||
TextColumn get achievementId => text()();
|
TextColumn get achievementId => text()();
|
||||||
|
|
||||||
// When achievement was unlocked
|
// When achievement was unlocked
|
||||||
DateTimeColumn get unlockedAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get unlockedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
// Progress (0.0 to 1.0)
|
// Progress (0.0 to 1.0)
|
||||||
RealColumn get progress => real().withDefault(const Constant(1.0))();
|
RealColumn get progress => real().withDefault(const Constant(1.0))();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'users.dart';
|
import 'users.dart';
|
||||||
import '../converters.dart' show generateUuid;
|
import '../converters.dart' show generateUuid;
|
||||||
import '../postgres_constants.dart';
|
|
||||||
|
|
||||||
/// Таблица Tokens - токены авторизации пользователей
|
/// Таблица Tokens - токены авторизации пользователей
|
||||||
class Tokens extends Table {
|
class Tokens extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get userId => text().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get userId => text()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
TextColumn get token => text().unique()();
|
TextColumn get token => text().unique()();
|
||||||
TextColumn get externalUserId => text()();
|
TextColumn get externalUserId => text()();
|
||||||
|
|
||||||
DateTimeColumn get created => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get created => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get expires => dateTime()();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get expires => customType(PgTypes.timestampWithTimezone)();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -25,17 +29,21 @@ class Tokens extends Table {
|
||||||
|
|
||||||
/// Таблица RefreshTokens - refresh токены для JWT
|
/// Таблица RefreshTokens - refresh токены для JWT
|
||||||
class RefreshTokens extends Table {
|
class RefreshTokens extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get userId => text().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get userId => text()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
TextColumn get jti => text().unique()(); // JWT ID
|
TextColumn get jti => text().unique()(); // JWT ID
|
||||||
|
|
||||||
BoolColumn get isBlacklisted => boolean()
|
BoolColumn get isBlacklisted => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get expiresAt => dateTime()();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get expiresAt => customType(PgTypes.timestampWithTimezone)();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -43,7 +51,9 @@ class RefreshTokens extends Table {
|
||||||
|
|
||||||
/// Таблица TelegramAuthCodes - коды для авторизации через Telegram
|
/// Таблица TelegramAuthCodes - коды для авторизации через Telegram
|
||||||
class TelegramAuthCodes extends Table {
|
class TelegramAuthCodes extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
TextColumn get code => text().unique()();
|
TextColumn get code => text().unique()();
|
||||||
TextColumn get telegramUserId => text()();
|
TextColumn get telegramUserId => text()();
|
||||||
|
|
@ -53,11 +63,13 @@ class TelegramAuthCodes extends Table {
|
||||||
|
|
||||||
BoolColumn get isUsed => boolean()
|
BoolColumn get isUsed => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
DateTimeColumn get usedAt => dateTime().nullable()();
|
Column<PgDateTime> get usedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.nullable()();
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get expiresAt => dateTime()();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get expiresAt => customType(PgTypes.timestampWithTimezone)();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../converters.dart' show generateUuid, StringListConverter, JsonListConverter;
|
import '../converters.dart' show generateUuid, StringListConverter, JsonListConverter;
|
||||||
import '../postgres_constants.dart';
|
|
||||||
import 'users.dart';
|
import 'users.dart';
|
||||||
|
|
||||||
/// Таблица DiscountCampaigns - кампании скидок
|
/// Таблица DiscountCampaigns - кампании скидок
|
||||||
class DiscountCampaigns extends Table {
|
class DiscountCampaigns extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get start => dateTime()();
|
Column<PgDateTime> get start => customType(PgTypes.timestampWithTimezone)();
|
||||||
DateTimeColumn get finish => dateTime()();
|
Column<PgDateTime> get finish => customType(PgTypes.timestampWithTimezone)();
|
||||||
|
|
||||||
// Статус (enum as string)
|
// Статус (enum as string)
|
||||||
TextColumn get status => text()(); // created, active, expired, disabled
|
TextColumn get status => text()(); // created, active, expired, disabled
|
||||||
|
|
@ -21,11 +23,13 @@ class DiscountCampaigns extends Table {
|
||||||
.map(const StringListConverter())();
|
.map(const StringListConverter())();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
BoolColumn get isDeleted => boolean()
|
BoolColumn get isDeleted => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -33,8 +37,11 @@ class DiscountCampaigns extends Table {
|
||||||
|
|
||||||
/// Таблица Discounts - скидки
|
/// Таблица Discounts - скидки
|
||||||
class Discounts extends Table {
|
class Discounts extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get campaignId => text().references(DiscountCampaigns, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get campaignId => text()
|
||||||
|
.references(DiscountCampaigns, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
// Процент скидки (0-100)
|
// Процент скидки (0-100)
|
||||||
RealColumn get discountPercent => real()();
|
RealColumn get discountPercent => real()();
|
||||||
|
|
@ -45,11 +52,13 @@ class Discounts extends Table {
|
||||||
.map(const JsonListConverter())();
|
.map(const JsonListConverter())();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
BoolColumn get isDeleted => boolean()
|
BoolColumn get isDeleted => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -57,10 +66,13 @@ class Discounts extends Table {
|
||||||
|
|
||||||
/// Junction table для связи Discounts ↔ UserDatas (many-to-many)
|
/// Junction table для связи Discounts ↔ UserDatas (many-to-many)
|
||||||
class DiscountUserDatas extends Table {
|
class DiscountUserDatas extends Table {
|
||||||
TextColumn get discountId => text().references(Discounts, #id, onDelete: KeyAction.cascade)();
|
TextColumn get discountId => text()
|
||||||
TextColumn get userId => text().references(Users, #id, onDelete: KeyAction.cascade)();
|
.references(Discounts, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get userId => text()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
DateTimeColumn get grantedAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get grantedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {discountId, userId};
|
Set<Column> get primaryKey => {discountId, userId};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../converters.dart' show generateUuid, IntListConverter, StringListConverter;
|
import '../converters.dart' show generateUuid, IntListConverter, StringListConverter;
|
||||||
import '../postgres_constants.dart';
|
|
||||||
|
|
||||||
/// Таблица CardPacks - наборы карточек
|
/// Таблица CardPacks - наборы карточек
|
||||||
class CardPacks extends Table {
|
class CardPacks extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
// Основная информация
|
// Основная информация
|
||||||
TextColumn get title => text()();
|
TextColumn get title => text()();
|
||||||
|
|
@ -21,7 +23,7 @@ class CardPacks extends Table {
|
||||||
IntColumn get order => integer().withDefault(const Constant(0))();
|
IntColumn get order => integer().withDefault(const Constant(0))();
|
||||||
BoolColumn get enabled => boolean()
|
BoolColumn get enabled => boolean()
|
||||||
.withDefault(const Constant(true))
|
.withDefault(const Constant(true))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
// Порядок карточек (JSON array of IDs)
|
// Порядок карточек (JSON array of IDs)
|
||||||
TextColumn get cardsOrder => text()
|
TextColumn get cardsOrder => text()
|
||||||
|
|
@ -38,11 +40,13 @@ class CardPacks extends Table {
|
||||||
TextColumn get currency => text().withDefault(const Constant('RUB'))();
|
TextColumn get currency => text().withDefault(const Constant('RUB'))();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
BoolColumn get isDeleted => boolean()
|
BoolColumn get isDeleted => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -50,8 +54,11 @@ class CardPacks extends Table {
|
||||||
|
|
||||||
/// Таблица GameCards - карточки для изучения
|
/// Таблица GameCards - карточки для изучения
|
||||||
class GameCards extends Table {
|
class GameCards extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get packId => text().references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get packId => text()
|
||||||
|
.references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
// Основной контент
|
// Основной контент
|
||||||
TextColumn get original => text()(); // слово на иностранном языке
|
TextColumn get original => text()(); // слово на иностранном языке
|
||||||
|
|
@ -70,11 +77,13 @@ class GameCards extends Table {
|
||||||
TextColumn get back => text().nullable()(); // дополнительный текст
|
TextColumn get back => text().nullable()(); // дополнительный текст
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
BoolColumn get isDeleted => boolean()
|
BoolColumn get isDeleted => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -82,13 +91,17 @@ class GameCards extends Table {
|
||||||
|
|
||||||
/// Таблица VoiceModels - голосовые файлы для карточек
|
/// Таблица VoiceModels - голосовые файлы для карточек
|
||||||
class VoiceModels extends Table {
|
class VoiceModels extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get cardId => text().references(GameCards, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get cardId => text()
|
||||||
|
.references(GameCards, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
TextColumn get voiceUrl => text()(); // URL аудиофайла
|
TextColumn get voiceUrl => text()(); // URL аудиофайла
|
||||||
TextColumn get language => text()(); // язык озвучки
|
TextColumn get language => text()(); // язык озвучки
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import '../converters.dart' show generateUuid, JsonListConverter, StringListConverter;
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../postgres_constants.dart';
|
import '../converters.dart' show JsonListConverter, StringListConverter;
|
||||||
import 'users.dart';
|
import 'users.dart';
|
||||||
|
|
||||||
/// Таблица Payments - платежи
|
/// Таблица Payments - платежи
|
||||||
class Payments extends Table {
|
class Payments extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get userId => text().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get userId => text()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
TextColumn get amount => text()();
|
TextColumn get amount => text()();
|
||||||
TextColumn get currency => text()();
|
TextColumn get currency => text()();
|
||||||
|
|
@ -18,7 +21,8 @@ class Payments extends Table {
|
||||||
TextColumn get externalToken => text().nullable()();
|
TextColumn get externalToken => text().nullable()();
|
||||||
TextColumn get meta => text().nullable()();
|
TextColumn get meta => text().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get date => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get date => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
// Продукты (JSON array)
|
// Продукты (JSON array)
|
||||||
TextColumn get products => text()
|
TextColumn get products => text()
|
||||||
|
|
@ -31,11 +35,13 @@ class Payments extends Table {
|
||||||
.map(const StringListConverter())();
|
.map(const StringListConverter())();
|
||||||
BoolColumn get subscription => boolean()
|
BoolColumn get subscription => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../converters.dart' show generateUuid, JsonListConverter, StringListConverter;
|
import '../converters.dart' show generateUuid, JsonListConverter, StringListConverter;
|
||||||
import '../postgres_constants.dart';
|
|
||||||
import 'users.dart';
|
import 'users.dart';
|
||||||
|
|
||||||
/// Таблица PromoCodesCampaigns - кампании промокодов
|
/// Таблица PromoCodesCampaigns - кампании промокодов
|
||||||
class PromoCodesCampaigns extends Table {
|
class PromoCodesCampaigns extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
TextColumn get template => text()();
|
TextColumn get template => text()();
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
|
|
@ -19,8 +21,8 @@ class PromoCodesCampaigns extends Table {
|
||||||
IntColumn get activationsPerUser => integer()();
|
IntColumn get activationsPerUser => integer()();
|
||||||
IntColumn get generationSize => integer()();
|
IntColumn get generationSize => integer()();
|
||||||
|
|
||||||
DateTimeColumn get start => dateTime()();
|
Column<PgDateTime> get start => customType(PgTypes.timestampWithTimezone)();
|
||||||
DateTimeColumn get finish => dateTime()();
|
Column<PgDateTime> get finish => customType(PgTypes.timestampWithTimezone)();
|
||||||
|
|
||||||
// Статус (enum as string)
|
// Статус (enum as string)
|
||||||
TextColumn get status => text()(); // created, preparing, ready, active, disabled
|
TextColumn get status => text()(); // created, preparing, ready, active, disabled
|
||||||
|
|
@ -31,11 +33,13 @@ class PromoCodesCampaigns extends Table {
|
||||||
.map(const StringListConverter())();
|
.map(const StringListConverter())();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
BoolColumn get isDeleted => boolean()
|
BoolColumn get isDeleted => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -43,18 +47,25 @@ class PromoCodesCampaigns extends Table {
|
||||||
|
|
||||||
/// Таблица PromoCodes - промокоды
|
/// Таблица PromoCodes - промокоды
|
||||||
class PromoCodes extends Table {
|
class PromoCodes extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get campaignId => text().references(PromoCodesCampaigns, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get campaignId => text()
|
||||||
|
.references(PromoCodesCampaigns, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
TextColumn get code => text().unique()();
|
TextColumn get code => text().unique()();
|
||||||
IntColumn get activations => integer().withDefault(const Constant(0))();
|
IntColumn get activations => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
// Индивидуальный промокод (связан с пользователем)
|
// Индивидуальный промокод (связан с пользователем)
|
||||||
TextColumn get userId => text().nullable().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get userId => text()
|
||||||
|
.nullable()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import '../postgres_constants.dart';
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'users.dart';
|
import 'users.dart';
|
||||||
import 'packs.dart' show CardPacks, GameCards, VoiceModels;
|
import 'packs.dart' show CardPacks, GameCards, VoiceModels;
|
||||||
|
|
||||||
/// Junction table для связи Users ↔ CardPacks (многие ко многим)
|
/// Junction table для связи Users ↔ CardPacks (многие ко многим)
|
||||||
/// Хранит информацию о том, какие паки куплены пользователем
|
/// Хранит информацию о том, какие паки куплены пользователем
|
||||||
class UserPacks extends Table {
|
class UserPacks extends Table {
|
||||||
TextColumn get userId => text().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get userId => text()
|
||||||
TextColumn get packId => text().references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get packId => text()
|
||||||
|
.references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
// Когда пользователь получил доступ к паку
|
// Когда пользователь получил доступ к паку
|
||||||
DateTimeColumn get grantedAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get grantedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
// Как пользователь получил пак (purchase, promo, free, admin)
|
// Как пользователь получил пак (purchase, promo, free, admin)
|
||||||
TextColumn get grantType => text().withDefault(const Constant('purchase'))();
|
TextColumn get grantType => text().withDefault(const Constant('purchase'))();
|
||||||
|
|
@ -22,8 +25,10 @@ class UserPacks extends Table {
|
||||||
/// Таблица PreviewCards - связь CardPacks с preview карточками
|
/// Таблица PreviewCards - связь CardPacks с preview карточками
|
||||||
/// Хранит какие карточки показывать в превью пака
|
/// Хранит какие карточки показывать в превью пака
|
||||||
class PreviewCards extends Table {
|
class PreviewCards extends Table {
|
||||||
TextColumn get packId => text().references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
TextColumn get packId => text()
|
||||||
TextColumn get cardId => text().references(GameCards, #id, onDelete: KeyAction.cascade)();
|
.references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get cardId => text()
|
||||||
|
.references(GameCards, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
IntColumn get order => integer().withDefault(const Constant(0))();
|
IntColumn get order => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
|
|
@ -34,8 +39,10 @@ class PreviewCards extends Table {
|
||||||
/// Таблица CardPackCards - связь CardPacks с GameCards (many-to-many)
|
/// Таблица CardPackCards - связь CardPacks с GameCards (many-to-many)
|
||||||
/// Хранит какие карточки принадлежат какому паку
|
/// Хранит какие карточки принадлежат какому паку
|
||||||
class CardPackCards extends Table {
|
class CardPackCards extends Table {
|
||||||
TextColumn get packId => text().references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
TextColumn get packId => text()
|
||||||
TextColumn get cardId => text().references(GameCards, #id, onDelete: KeyAction.cascade)();
|
.references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get cardId => text()
|
||||||
|
.references(GameCards, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
IntColumn get order => integer().withDefault(const Constant(0))();
|
IntColumn get order => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
|
|
@ -46,8 +53,10 @@ class CardPackCards extends Table {
|
||||||
/// Таблица CardVoices - связь GameCards с VoiceModels (many-to-many)
|
/// Таблица CardVoices - связь GameCards с VoiceModels (many-to-many)
|
||||||
/// Хранит какие голосовые файлы принадлежат какой карточке
|
/// Хранит какие голосовые файлы принадлежат какой карточке
|
||||||
class CardVoices extends Table {
|
class CardVoices extends Table {
|
||||||
TextColumn get cardId => text().references(GameCards, #id, onDelete: KeyAction.cascade)();
|
TextColumn get cardId => text()
|
||||||
TextColumn get voiceId => text().references(VoiceModels, #id, onDelete: KeyAction.cascade)();
|
.references(GameCards, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get voiceId => text()
|
||||||
|
.references(VoiceModels, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {cardId, voiceId};
|
Set<Column> get primaryKey => {cardId, voiceId};
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,21 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'users.dart';
|
import 'users.dart';
|
||||||
import '../converters.dart' show generateUuid;
|
import '../converters.dart' show generateUuid;
|
||||||
import '../postgres_constants.dart';
|
|
||||||
|
|
||||||
/// Таблица StudySessions - сессии изучения
|
/// Таблица StudySessions - сессии изучения
|
||||||
class StudySessions extends Table {
|
class StudySessions extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get userId => text().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get userId => text()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
TextColumn get sessionId => text().nullable().unique()();
|
TextColumn get sessionId => text().nullable().unique()();
|
||||||
|
|
||||||
DateTimeColumn get startTime => dateTime()();
|
Column<PgDateTime> get startTime => customType(PgTypes.timestampWithTimezone)();
|
||||||
DateTimeColumn get endTime => dateTime().nullable()();
|
Column<PgDateTime> get endTime => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.nullable()();
|
||||||
|
|
||||||
IntColumn get wordsLearned => integer().withDefault(const Constant(0))();
|
IntColumn get wordsLearned => integer().withDefault(const Constant(0))();
|
||||||
IntColumn get testsCompleted => integer().withDefault(const Constant(0))();
|
IntColumn get testsCompleted => integer().withDefault(const Constant(0))();
|
||||||
|
|
@ -21,8 +25,10 @@ class StudySessions extends Table {
|
||||||
TextColumn get testId => text().nullable()();
|
TextColumn get testId => text().nullable()();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../converters.dart' show JsonMapConverter, JsonListConverter;
|
import '../converters.dart' show JsonMapConverter, JsonListConverter;
|
||||||
import '../postgres_constants.dart';
|
|
||||||
import 'users.dart';
|
import 'users.dart';
|
||||||
|
|
||||||
/// Таблица SubscriptionPlans - планы подписки
|
/// Таблица SubscriptionPlans - планы подписки
|
||||||
class SubscriptionPlans extends Table {
|
class SubscriptionPlans extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
// UI информация (JSON)
|
// UI информация (JSON)
|
||||||
TextColumn get ui => text().nullable().map(const JsonMapConverter())();
|
TextColumn get ui => text().nullable().map(const JsonMapConverter())();
|
||||||
|
|
@ -26,11 +27,13 @@ class SubscriptionPlans extends Table {
|
||||||
TextColumn get paymentSystem => text()(); // enum as string
|
TextColumn get paymentSystem => text()(); // enum as string
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
BoolColumn get isDeleted => boolean()
|
BoolColumn get isDeleted => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -38,11 +41,15 @@ class SubscriptionPlans extends Table {
|
||||||
|
|
||||||
/// Таблица UserSubscriptions - подписки пользователей
|
/// Таблица UserSubscriptions - подписки пользователей
|
||||||
class UserSubscriptions extends Table {
|
class UserSubscriptions extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get userId => text().unique().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get userId => text()
|
||||||
|
.unique()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
DateTimeColumn get start => dateTime()();
|
Column<PgDateTime> get start => customType(PgTypes.timestampWithTimezone)();
|
||||||
DateTimeColumn get finish => dateTime()();
|
Column<PgDateTime> get finish => customType(PgTypes.timestampWithTimezone)();
|
||||||
|
|
||||||
// Функции подписки (JSON array)
|
// Функции подписки (JSON array)
|
||||||
TextColumn get features => text()
|
TextColumn get features => text()
|
||||||
|
|
@ -50,8 +57,10 @@ class UserSubscriptions extends Table {
|
||||||
.map(const JsonListConverter())();
|
.map(const JsonListConverter())();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../converters.dart' show generateUuid, JsonListConverter, JsonMapConverter, StringListConverter;
|
import '../converters.dart' show generateUuid, JsonListConverter, JsonMapConverter, StringListConverter;
|
||||||
import '../postgres_constants.dart';
|
|
||||||
import 'users.dart';
|
import 'users.dart';
|
||||||
|
|
||||||
/// Таблица Tasks - задачи системы
|
/// Таблица Tasks - задачи системы
|
||||||
class Tasks extends Table {
|
class Tasks extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
IntColumn get minCycleMillis => integer()();
|
IntColumn get minCycleMillis => integer()();
|
||||||
|
|
@ -15,11 +17,14 @@ class Tasks extends Table {
|
||||||
|
|
||||||
TextColumn get status => text().nullable()();
|
TextColumn get status => text().nullable()();
|
||||||
TextColumn get description => text().nullable()();
|
TextColumn get description => text().nullable()();
|
||||||
DateTimeColumn get lastExecution => dateTime().nullable()();
|
Column<PgDateTime> get lastExecution => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.nullable()();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -27,7 +32,9 @@ class Tasks extends Table {
|
||||||
|
|
||||||
/// Таблица UserTasks - задачи пользователей
|
/// Таблица UserTasks - задачи пользователей
|
||||||
class UserTasks extends Table {
|
class UserTasks extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
TextColumn get title => text()();
|
TextColumn get title => text()();
|
||||||
TextColumn get description => text()();
|
TextColumn get description => text()();
|
||||||
|
|
@ -40,9 +47,11 @@ class UserTasks extends Table {
|
||||||
.withDefault(const Constant('[]'))
|
.withDefault(const Constant('[]'))
|
||||||
.map(const JsonListConverter())();
|
.map(const JsonListConverter())();
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get expiresAt => dateTime()();
|
.withDefault(now())();
|
||||||
DateTimeColumn get completedAt => dateTime().nullable()();
|
Column<PgDateTime> get expiresAt => customType(PgTypes.timestampWithTimezone)();
|
||||||
|
Column<PgDateTime> get completedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.nullable()();
|
||||||
|
|
||||||
TextColumn get proofUrl => text().nullable()();
|
TextColumn get proofUrl => text().nullable()();
|
||||||
TextColumn get instructions => text().nullable()();
|
TextColumn get instructions => text().nullable()();
|
||||||
|
|
@ -55,7 +64,8 @@ class UserTasks extends Table {
|
||||||
TextColumn get imageUrl => text().nullable()();
|
TextColumn get imageUrl => text().nullable()();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -63,8 +73,11 @@ class UserTasks extends Table {
|
||||||
|
|
||||||
/// Таблица UserTaskProgresses - прогресс выполнения задач пользователями
|
/// Таблица UserTaskProgresses - прогресс выполнения задач пользователями
|
||||||
class UserTaskProgresses extends Table {
|
class UserTaskProgresses extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get userId => text().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get userId => text()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
TextColumn get taskId => text()(); // Reference to UserTasks, but not FK to avoid circular deps
|
TextColumn get taskId => text()(); // Reference to UserTasks, but not FK to avoid circular deps
|
||||||
|
|
||||||
// Прогресс (JSON)
|
// Прогресс (JSON)
|
||||||
|
|
@ -72,8 +85,10 @@ class UserTaskProgresses extends Table {
|
||||||
.withDefault(const Constant('{}'))
|
.withDefault(const Constant('{}'))
|
||||||
.map(const JsonMapConverter())();
|
.map(const JsonMapConverter())();
|
||||||
|
|
||||||
DateTimeColumn get startedAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get startedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -81,8 +96,11 @@ class UserTaskProgresses extends Table {
|
||||||
|
|
||||||
/// Таблица UserTaskResults - результаты выполнения задач
|
/// Таблица UserTaskResults - результаты выполнения задач
|
||||||
class UserTaskResults extends Table {
|
class UserTaskResults extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get userId => text().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get userId => text()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
TextColumn get taskId => text()(); // Reference to UserTasks
|
TextColumn get taskId => text()(); // Reference to UserTasks
|
||||||
|
|
||||||
// Результаты (JSON)
|
// Результаты (JSON)
|
||||||
|
|
@ -90,10 +108,12 @@ class UserTaskResults extends Table {
|
||||||
.withDefault(const Constant('{}'))
|
.withDefault(const Constant('{}'))
|
||||||
.map(const JsonMapConverter())();
|
.map(const JsonMapConverter())();
|
||||||
|
|
||||||
DateTimeColumn get completedAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get completedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,27 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../converters.dart' show generateUuid;
|
import '../converters.dart' show generateUuid;
|
||||||
import '../postgres_constants.dart';
|
|
||||||
|
|
||||||
/// Таблица ShareRequests - запросы на шаринг через Telegram
|
/// Таблица ShareRequests - запросы на шаринг через Telegram
|
||||||
class ShareRequests extends Table {
|
class ShareRequests extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
TextColumn get telegramUserId => text()();
|
TextColumn get telegramUserId => text()();
|
||||||
TextColumn get telegramUsername => text().nullable()();
|
TextColumn get telegramUsername => text().nullable()();
|
||||||
|
|
||||||
TextColumn get sharedCardId => text().nullable()();
|
TextColumn get sharedCardId => text()
|
||||||
|
.nullable()();
|
||||||
|
|
||||||
DateTimeColumn get requestedAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get requestedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../converters.dart' show generateUuid, JsonMapConverter;
|
import '../converters.dart' show generateUuid, JsonMapConverter;
|
||||||
import '../postgres_constants.dart';
|
|
||||||
import 'packs.dart';
|
import 'packs.dart';
|
||||||
|
|
||||||
/// Таблица Tests - тесты
|
/// Таблица Tests - тесты
|
||||||
class Tests extends Table {
|
class Tests extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
TextColumn get color => text().nullable()();
|
TextColumn get color => text().nullable()();
|
||||||
|
|
@ -15,11 +17,13 @@ class Tests extends Table {
|
||||||
TextColumn get timeSubtitle => text().nullable()();
|
TextColumn get timeSubtitle => text().nullable()();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
BoolColumn get isDeleted => boolean()
|
BoolColumn get isDeleted => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -27,16 +31,21 @@ class Tests extends Table {
|
||||||
|
|
||||||
/// Таблица TestQuestions - вопросы тестов
|
/// Таблица TestQuestions - вопросы тестов
|
||||||
class TestQuestions extends Table {
|
class TestQuestions extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get testId => text().references(Tests, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get testId => text()
|
||||||
|
.references(Tests, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
// Тип вопроса (enum as string)
|
// Тип вопроса (enum as string)
|
||||||
TextColumn get questionType => text()();
|
TextColumn get questionType => text()();
|
||||||
TextColumn get body => text()();
|
TextColumn get body => text()();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -44,8 +53,10 @@ class TestQuestions extends Table {
|
||||||
|
|
||||||
/// Таблица TestPackRelations - связь Tests с CardPacks (many-to-many)
|
/// Таблица TestPackRelations - связь Tests с CardPacks (many-to-many)
|
||||||
class TestPackRelations extends Table {
|
class TestPackRelations extends Table {
|
||||||
TextColumn get testId => text().references(Tests, #id, onDelete: KeyAction.cascade)();
|
TextColumn get testId => text()
|
||||||
TextColumn get packId => text().references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
.references(Tests, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get packId => text()
|
||||||
|
.references(CardPacks, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {testId, packId};
|
Set<Column> get primaryKey => {testId, packId};
|
||||||
|
|
@ -53,20 +64,26 @@ class TestPackRelations extends Table {
|
||||||
|
|
||||||
/// Таблица TestStatistics - статистика прохождения тестов
|
/// Таблица TestStatistics - статистика прохождения тестов
|
||||||
class TestStatistics extends Table {
|
class TestStatistics extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
TextColumn get userId => text()(); // Reference to Users, but not FK to avoid circular deps
|
TextColumn get userId => text()(); // Reference to Users, but not FK to avoid circular deps
|
||||||
TextColumn get testId => text().references(Tests, #id, onDelete: KeyAction.cascade)();
|
TextColumn get testId => text()
|
||||||
|
.references(Tests, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
// Результаты (JSON)
|
// Результаты (JSON)
|
||||||
TextColumn get results => text()
|
TextColumn get results => text()
|
||||||
.withDefault(const Constant('{}'))
|
.withDefault(const Constant('{}'))
|
||||||
.map(const JsonMapConverter())();
|
.map(const JsonMapConverter())();
|
||||||
|
|
||||||
DateTimeColumn get completedAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get completedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import '../converters.dart';
|
import '../converters.dart';
|
||||||
import '../postgres_constants.dart';
|
|
||||||
|
|
||||||
/// Таблица Users - основная информация о пользователях
|
/// Таблица Users - основная информация о пользователях
|
||||||
class Users extends Table {
|
class Users extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
TextColumn get externalUserId => text().unique()();
|
TextColumn get externalUserId => text().unique()();
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
TextColumn get email => text().nullable()();
|
TextColumn get email => text().nullable()();
|
||||||
BoolColumn get admin => boolean()
|
BoolColumn get admin => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
// UserSettings (JSON)
|
// UserSettings (JSON)
|
||||||
TextColumn get userSettings => text().nullable()();
|
TextColumn get userSettings => text().nullable()();
|
||||||
|
|
@ -21,11 +23,13 @@ class Users extends Table {
|
||||||
.map(const StringListConverter())();
|
.map(const StringListConverter())();
|
||||||
|
|
||||||
// Audit fields
|
// Audit fields
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
BoolColumn get isDeleted => boolean()
|
BoolColumn get isDeleted => boolean()
|
||||||
.withDefault(const Constant(false))
|
.withDefault(const Constant(false))
|
||||||
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN, не нужен CHECK
|
.customConstraint('')(); // PostgreSQL использует нативный BOOLEAN
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
@ -38,8 +42,12 @@ class Users extends Table {
|
||||||
|
|
||||||
/// Таблица UserDatas - расширенная информация о пользователе
|
/// Таблица UserDatas - расширенная информация о пользователе
|
||||||
class UserDatas extends Table {
|
class UserDatas extends Table {
|
||||||
TextColumn get id => text().withDefault(const Constant('gen_random_uuid()'))();
|
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
|
||||||
TextColumn get userId => text().unique().references(Users, #id, onDelete: KeyAction.cascade)();
|
TextColumn get id => text()
|
||||||
|
.withDefault(const CustomExpression('gen_random_uuid()::text'))();
|
||||||
|
TextColumn get userId => text()
|
||||||
|
.unique()
|
||||||
|
.references(Users, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
// Статистика
|
// Статистика
|
||||||
IntColumn get totalStudyTimeMinutes => integer().withDefault(const Constant(0))();
|
IntColumn get totalStudyTimeMinutes => integer().withDefault(const Constant(0))();
|
||||||
|
|
@ -49,8 +57,10 @@ class UserDatas extends Table {
|
||||||
IntColumn get totalTests => integer().withDefault(const Constant(0))();
|
IntColumn get totalTests => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
// Временные метки
|
// Временные метки
|
||||||
DateTimeColumn get lastTimeOnline => dateTime().nullable()();
|
Column<PgDateTime> get lastTimeOnline => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get registrationDate => dateTime().withDefault(currentTimestamp)();
|
.nullable()();
|
||||||
|
Column<PgDateTime> get registrationDate => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
TextColumn get lastTestSessionToken => text().nullable()();
|
TextColumn get lastTestSessionToken => text().nullable()();
|
||||||
|
|
||||||
// Сложные структуры (JSON)
|
// Сложные структуры (JSON)
|
||||||
|
|
@ -79,8 +89,10 @@ class UserDatas extends Table {
|
||||||
.map(const JsonMapConverter())();
|
.map(const JsonMapConverter())();
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentTimestamp)();
|
Column<PgDateTime> get createdAt => customType(PgTypes.timestampWithTimezone)
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentTimestamp)();
|
.withDefault(now())();
|
||||||
|
Column<PgDateTime> get updatedAt => customType(PgTypes.timestampWithTimezone)
|
||||||
|
.withDefault(now())();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart' as drift;
|
import 'package:drift/drift.dart' as drift;
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:mnemo_cards_backend/database/daos/discount_dao.dart';
|
import 'package:mnemo_cards_backend/database/daos/discount_dao.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
|
|
@ -30,8 +31,8 @@ extension DiscountCampaignToDto on DiscountCampaign {
|
||||||
|
|
||||||
return DiscountCampaignDto(
|
return DiscountCampaignDto(
|
||||||
id: id,
|
id: id,
|
||||||
start: start,
|
start: start.dateTime,
|
||||||
finish: finish,
|
finish: finish.dateTime,
|
||||||
status: statusEnum,
|
status: statusEnum,
|
||||||
name: name,
|
name: name,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
|
|
@ -80,8 +81,8 @@ extension DiscountCampaignDtoToCompanion on DiscountCampaignDto {
|
||||||
return DiscountCampaignsCompanion.insert(
|
return DiscountCampaignsCompanion.insert(
|
||||||
id: id != null ? drift.Value(id!) : const drift.Value.absent(),
|
id: id != null ? drift.Value(id!) : const drift.Value.absent(),
|
||||||
name: drift.Value(name),
|
name: drift.Value(name),
|
||||||
start: start,
|
start: PgDateTime(start),
|
||||||
finish: finish,
|
finish: PgDateTime(finish),
|
||||||
status: statusStr,
|
status: statusStr,
|
||||||
tags: drift.Value(tags),
|
tags: drift.Value(tags),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,12 @@ class DiscountsManager {
|
||||||
String? targetStatus;
|
String? targetStatus;
|
||||||
|
|
||||||
if (campaign.status == 'created' &&
|
if (campaign.status == 'created' &&
|
||||||
now.isBetween(campaign.start, campaign.finish)) {
|
now.isBetween(campaign.start.dateTime, campaign.finish.dateTime)) {
|
||||||
targetStatus = 'active';
|
targetStatus = 'active';
|
||||||
} else if (campaign.status == 'active') {
|
} else if (campaign.status == 'active') {
|
||||||
if (now.isBefore(campaign.start)) {
|
if (now.isBefore(campaign.start.dateTime)) {
|
||||||
targetStatus = 'created';
|
targetStatus = 'created';
|
||||||
} else if (now.isAfter(campaign.finish)) {
|
} else if (now.isAfter(campaign.finish.dateTime)) {
|
||||||
targetStatus = 'expired';
|
targetStatus = 'expired';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +59,8 @@ class DiscountsManager {
|
||||||
|
|
||||||
Future<DiscountError?> applyCampaign(DiscountCampaign campaign) async {
|
Future<DiscountError?> applyCampaign(DiscountCampaign campaign) async {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (!now.isBetween(campaign.start, campaign.finish)) {
|
if (!now.isBetween(campaign.start.dateTime, campaign.finish.dateTime)) {
|
||||||
if (now.isBefore(campaign.start)) {
|
if (now.isBefore(campaign.start.dateTime)) {
|
||||||
return DiscountError(DiscountErrorType.notStarted);
|
return DiscountError(DiscountErrorType.notStarted);
|
||||||
}
|
}
|
||||||
return DiscountError(DiscountErrorType.expired);
|
return DiscountError(DiscountErrorType.expired);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/api/purchase/payment_manager.dart';
|
import 'package:mnemo_cards_backend/api/purchase/payment_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
|
|
@ -47,8 +48,8 @@ class PromoCodesManager {
|
||||||
activationsPerCode: campaign.activationsPerCode,
|
activationsPerCode: campaign.activationsPerCode,
|
||||||
activationsPerUser: campaign.activationsPerUser,
|
activationsPerUser: campaign.activationsPerUser,
|
||||||
generationSize: campaign.generationSize,
|
generationSize: campaign.generationSize,
|
||||||
start: campaign.start,
|
start: campaign.start.dateTime,
|
||||||
finish: campaign.finish,
|
finish: campaign.finish.dateTime,
|
||||||
status: _parseCampaignStatus(campaign.status),
|
status: _parseCampaignStatus(campaign.status),
|
||||||
tags: campaign.tags,
|
tags: campaign.tags,
|
||||||
promoCodes: promoCodes,
|
promoCodes: promoCodes,
|
||||||
|
|
@ -77,8 +78,8 @@ class PromoCodesManager {
|
||||||
activationsPerCode: campaign.activationsPerCode,
|
activationsPerCode: campaign.activationsPerCode,
|
||||||
activationsPerUser: campaign.activationsPerUser,
|
activationsPerUser: campaign.activationsPerUser,
|
||||||
generationSize: campaign.generationSize,
|
generationSize: campaign.generationSize,
|
||||||
start: campaign.start,
|
start: campaign.start.dateTime,
|
||||||
finish: campaign.finish,
|
finish: campaign.finish.dateTime,
|
||||||
status: _parseCampaignStatus(campaign.status),
|
status: _parseCampaignStatus(campaign.status),
|
||||||
tags: campaign.tags,
|
tags: campaign.tags,
|
||||||
promoCodes: promoCodes,
|
promoCodes: promoCodes,
|
||||||
|
|
@ -95,8 +96,8 @@ class PromoCodesManager {
|
||||||
activationsPerCode: dto.activationsPerCode,
|
activationsPerCode: dto.activationsPerCode,
|
||||||
activationsPerUser: dto.activationsPerUser,
|
activationsPerUser: dto.activationsPerUser,
|
||||||
generationSize: dto.generationSize,
|
generationSize: dto.generationSize,
|
||||||
start: dto.start,
|
start: PgDateTime(dto.start),
|
||||||
finish: dto.finish,
|
finish: PgDateTime(dto.finish),
|
||||||
status: dto.status.name,
|
status: dto.status.name,
|
||||||
tags: drift.Value(dto.tags),
|
tags: drift.Value(dto.tags),
|
||||||
);
|
);
|
||||||
|
|
@ -119,11 +120,11 @@ class PromoCodesManager {
|
||||||
activationsPerCode: dto.activationsPerCode,
|
activationsPerCode: dto.activationsPerCode,
|
||||||
activationsPerUser: dto.activationsPerUser,
|
activationsPerUser: dto.activationsPerUser,
|
||||||
generationSize: dto.generationSize,
|
generationSize: dto.generationSize,
|
||||||
start: dto.start,
|
start: PgDateTime(dto.start),
|
||||||
finish: dto.finish,
|
finish: PgDateTime(dto.finish),
|
||||||
status: dto.status.name,
|
status: dto.status.name,
|
||||||
tags: dto.tags,
|
tags: dto.tags,
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: PgDateTime(DateTime.now()),
|
||||||
);
|
);
|
||||||
|
|
||||||
await _db.promoCodeDao.updateCampaign(updated);
|
await _db.promoCodeDao.updateCampaign(updated);
|
||||||
|
|
@ -137,7 +138,7 @@ class PromoCodesManager {
|
||||||
// Soft delete - mark as deleted
|
// Soft delete - mark as deleted
|
||||||
await _db.promoCodeDao.updateCampaign(existing.copyWith(
|
await _db.promoCodeDao.updateCampaign(existing.copyWith(
|
||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: PgDateTime(DateTime.now()),
|
||||||
));
|
));
|
||||||
|
|
||||||
return null; // No error
|
return null; // No error
|
||||||
|
|
@ -168,8 +169,8 @@ class PromoCodesManager {
|
||||||
|
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (campaign.status != 'active' ||
|
if (campaign.status != 'active' ||
|
||||||
campaign.start.isAfter(now) ||
|
campaign.start.dateTime.isAfter(now) ||
|
||||||
campaign.finish.isBefore(now)) {
|
campaign.finish.dateTime.isBefore(now)) {
|
||||||
return {'valid': false, 'message': 'Promo code expired'};
|
return {'valid': false, 'message': 'Promo code expired'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,7 +255,7 @@ class PromoCodesManager {
|
||||||
await _db.promoCodeDao.updateCampaign(
|
await _db.promoCodeDao.updateCampaign(
|
||||||
campaign.copyWith(
|
campaign.copyWith(
|
||||||
status: 'active',
|
status: 'active',
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: PgDateTime(DateTime.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
|
|
@ -57,7 +58,7 @@ class AchievementManager {
|
||||||
final definition = AchievementDefinitions.getById(userAchievement.achievementId);
|
final definition = AchievementDefinitions.getById(userAchievement.achievementId);
|
||||||
if (definition != null) {
|
if (definition != null) {
|
||||||
achievements.add(definition.copyWith(
|
achievements.add(definition.copyWith(
|
||||||
unlockedAt: userAchievement.unlockedAt,
|
unlockedAt: userAchievement.unlockedAt.dateTime,
|
||||||
progress: userAchievement.progress,
|
progress: userAchievement.progress,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +91,7 @@ class AchievementManager {
|
||||||
final companion = UserAchievementsCompanion.insert(
|
final companion = UserAchievementsCompanion.insert(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
achievementId: achievementId,
|
achievementId: achievementId,
|
||||||
unlockedAt: Value(DateTime.now()),
|
unlockedAt: Value(PgDateTime(DateTime.now())),
|
||||||
progress: Value(1.0),
|
progress: Value(1.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
import 'package:drift/drift.dart' as drift;
|
import 'package:drift/drift.dart' as drift;
|
||||||
|
|
@ -45,7 +46,7 @@ class SessionTracker {
|
||||||
|
|
||||||
// Create new session
|
// Create new session
|
||||||
final sessionId = _generateSessionId();
|
final sessionId = _generateSessionId();
|
||||||
final now = DateTime.now();
|
final now = PgDateTime(DateTime.now());
|
||||||
|
|
||||||
await _db.statisticsDao.createSession(
|
await _db.statisticsDao.createSession(
|
||||||
StudySessionsCompanion.insert(
|
StudySessionsCompanion.insert(
|
||||||
|
|
@ -116,7 +117,7 @@ class SessionTracker {
|
||||||
wordsLearned: wordsLearned ?? session.wordsLearned,
|
wordsLearned: wordsLearned ?? session.wordsLearned,
|
||||||
testsCompleted: testsCompleted ?? session.testsCompleted,
|
testsCompleted: testsCompleted ?? session.testsCompleted,
|
||||||
accuracy: accuracy ?? session.accuracy,
|
accuracy: accuracy ?? session.accuracy,
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: PgDateTime(DateTime.now()),
|
||||||
);
|
);
|
||||||
|
|
||||||
await _db.statisticsDao.updateSession(updatedSession);
|
await _db.statisticsDao.updateSession(updatedSession);
|
||||||
|
|
@ -155,7 +156,7 @@ class SessionTracker {
|
||||||
|
|
||||||
if (session != null &&
|
if (session != null &&
|
||||||
session.endTime == null &&
|
session.endTime == null &&
|
||||||
now.difference(session.startTime).inMinutes > _sessionTimeout.inMinutes) {
|
now.difference(session.startTime.dateTime).inMinutes > _sessionTimeout.inMinutes) {
|
||||||
expiredSessions.add(sessionId);
|
expiredSessions.add(sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +191,7 @@ class SessionTracker {
|
||||||
final session = await _db.statisticsDao.getSessionBySessionId(sessionId);
|
final session = await _db.statisticsDao.getSessionBySessionId(sessionId);
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
await _db.statisticsDao.updateSession(
|
await _db.statisticsDao.updateSession(
|
||||||
session.copyWith(updatedAt: DateTime.now()),
|
session.copyWith(updatedAt: PgDateTime(DateTime.now())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:drift/drift.dart' as drift;
|
import 'package:drift/drift.dart' as drift;
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
|
|
@ -64,7 +65,7 @@ class TaskManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновить статус задачи
|
// Обновить статус задачи
|
||||||
final now = DateTime.now();
|
final now = PgDateTime(DateTime.now());
|
||||||
await _db.taskDao.updateUserTask(
|
await _db.taskDao.updateUserTask(
|
||||||
task.copyWith(
|
task.copyWith(
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
|
|
@ -77,7 +78,7 @@ class TaskManager {
|
||||||
UserTaskResultsCompanion.insert(
|
UserTaskResultsCompanion.insert(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
taskId: taskId,
|
taskId: taskId,
|
||||||
results: drift.Value({'completed': true, 'completedAt': now.toIso8601String()}),
|
results: drift.Value({'completed': true, 'completedAt': now.dateTime.toIso8601String()}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:drift/drift.dart' as drift;
|
import 'package:drift/drift.dart' as drift;
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
|
|
@ -48,7 +49,7 @@ class UserManager {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final token = await _db.userDao.getTokenByUserId(user.id!);
|
final token = await _db.userDao.getTokenByUserId(user.id!);
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
if (token.expires.isAfter(now)) {
|
if (token.expires.dateTime.isAfter(now)) {
|
||||||
return token.token;
|
return token.token;
|
||||||
}
|
}
|
||||||
await _db.userDao.deleteToken(token.id);
|
await _db.userDao.deleteToken(token.id);
|
||||||
|
|
@ -59,7 +60,7 @@ class UserManager {
|
||||||
token: userToken,
|
token: userToken,
|
||||||
externalUserId: externalId,
|
externalUserId: externalId,
|
||||||
userId: user.id!,
|
userId: user.id!,
|
||||||
expires: now.add(const Duration(days: 360)),
|
expires: PgDateTime(now.add(const Duration(days: 360))),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return userToken;
|
return userToken;
|
||||||
|
|
@ -71,7 +72,7 @@ class UserManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (token.expires.isBefore(now)) {
|
if (token.expires.dateTime.isBefore(now)) {
|
||||||
await _db.userDao.deleteToken(token.id);
|
await _db.userDao.deleteToken(token.id);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -98,8 +99,8 @@ class UserManager {
|
||||||
await _db.userDao.updateUserDataPartial(
|
await _db.userDao.updateUserDataPartial(
|
||||||
UserDatasCompanion(
|
UserDatasCompanion(
|
||||||
userId: drift.Value(userId),
|
userId: drift.Value(userId),
|
||||||
lastTimeOnline: drift.Value(lastOnline),
|
lastTimeOnline: drift.Value(PgDateTime(lastOnline)),
|
||||||
updatedAt: drift.Value(DateTime.now()),
|
updatedAt: drift.Value(PgDateTime(DateTime.now())),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +138,7 @@ class UserManager {
|
||||||
await _db.userDao.createUserData(
|
await _db.userDao.createUserData(
|
||||||
UserDatasCompanion.insert(
|
UserDatasCompanion.insert(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
registrationDate: drift.Value(DateTime.now()),
|
registrationDate: drift.Value(PgDateTime(DateTime.now())),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -166,7 +167,7 @@ class UserManager {
|
||||||
UsersCompanion(
|
UsersCompanion(
|
||||||
id: drift.Value(user.id!),
|
id: drift.Value(user.id!),
|
||||||
userSettings: drift.Value(jsonEncode(settings.toJson())),
|
userSettings: drift.Value(jsonEncode(settings.toJson())),
|
||||||
updatedAt: drift.Value(DateTime.now()),
|
updatedAt: drift.Value(PgDateTime(DateTime.now())),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +220,7 @@ class UserManager {
|
||||||
// Обновить существующую статистику
|
// Обновить существующую статистику
|
||||||
final updatedStat = existingStat.copyWith(
|
final updatedStat = existingStat.copyWith(
|
||||||
results: drift.Value(updatedResults),
|
results: drift.Value(updatedResults),
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: PgDateTime(DateTime.now()),
|
||||||
);
|
);
|
||||||
await _db.testDao.updateTestStatistics(updatedStat);
|
await _db.testDao.updateTestStatistics(updatedStat);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -229,7 +230,7 @@ class UserManager {
|
||||||
userId: user.id!,
|
userId: user.id!,
|
||||||
testId: testStat.testId,
|
testId: testStat.testId,
|
||||||
results: drift.Value(updatedResults),
|
results: drift.Value(updatedResults),
|
||||||
completedAt: drift.Value(DateTime.now()),
|
completedAt: drift.Value(PgDateTime(DateTime.now())),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -259,8 +260,8 @@ class UserManager {
|
||||||
studyDates: drift.Value(studyDates),
|
studyDates: drift.Value(studyDates),
|
||||||
currentStreak: drift.Value(currentStreak),
|
currentStreak: drift.Value(currentStreak),
|
||||||
longestStreak: drift.Value(longestStreak),
|
longestStreak: drift.Value(longestStreak),
|
||||||
lastTimeOnline: drift.Value(now),
|
lastTimeOnline: drift.Value(PgDateTime(now)),
|
||||||
updatedAt: drift.Value(now),
|
updatedAt: drift.Value(PgDateTime(now)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:drift_postgres/drift_postgres.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:drift/drift.dart' as drift;
|
import 'package:drift/drift.dart' as drift;
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
|
|
@ -44,7 +45,7 @@ class UserManager {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final token = await _db.userDao.getTokenByUserId(user.id!);
|
final token = await _db.userDao.getTokenByUserId(user.id!);
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
if (token.expires.isAfter(now)) {
|
if (token.expires.dateTime.isAfter(now)) {
|
||||||
return token.token;
|
return token.token;
|
||||||
}
|
}
|
||||||
await _db.userDao.deleteToken(token.id);
|
await _db.userDao.deleteToken(token.id);
|
||||||
|
|
@ -55,7 +56,7 @@ class UserManager {
|
||||||
token: userToken,
|
token: userToken,
|
||||||
externalUserId: externalId,
|
externalUserId: externalId,
|
||||||
userId: user.id!,
|
userId: user.id!,
|
||||||
expires: now.add(const Duration(days: 360)),
|
expires: PgDateTime(now.add(const Duration(days: 360))),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return userToken;
|
return userToken;
|
||||||
|
|
@ -67,7 +68,7 @@ class UserManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (token.expires.isBefore(now)) {
|
if (token.expires.dateTime.isBefore(now)) {
|
||||||
await _db.userDao.deleteToken(token.id);
|
await _db.userDao.deleteToken(token.id);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -94,8 +95,8 @@ class UserManager {
|
||||||
await _db.userDao.updateUserDataPartial(
|
await _db.userDao.updateUserDataPartial(
|
||||||
UserDatasCompanion(
|
UserDatasCompanion(
|
||||||
userId: drift.Value(userId),
|
userId: drift.Value(userId),
|
||||||
lastTimeOnline: drift.Value(lastOnline),
|
lastTimeOnline: drift.Value(PgDateTime(lastOnline)),
|
||||||
updatedAt: drift.Value(DateTime.now()),
|
updatedAt: drift.Value(PgDateTime(DateTime.now())),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -133,7 +134,7 @@ class UserManager {
|
||||||
await _db.userDao.createUserData(
|
await _db.userDao.createUserData(
|
||||||
UserDatasCompanion.insert(
|
UserDatasCompanion.insert(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
registrationDate: drift.Value(DateTime.now()),
|
registrationDate: drift.Value(PgDateTime(DateTime.now())),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.13.0"
|
||||||
auth_header:
|
auth_header:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -229,10 +229,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.7"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -595,10 +595,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: postgres
|
name: postgres
|
||||||
sha256: "83ba7afb0e778cc1f373e514c6110d323b81dfbaced774d3a7f4a0e21536eb45"
|
sha256: fefbbfe749c6130e5096588b9c4459173684c695952cd7636ab19be76f255469
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.8"
|
version: "3.5.9"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -639,22 +639,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sasl_scram:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sasl_scram
|
|
||||||
sha256: a47207a436eb650f8fdcf54a2e2587b850dc3caef9973ce01f332b07a6fc9cb9
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.1"
|
|
||||||
saslprep:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: saslprep
|
|
||||||
sha256: "3d421d10be9513bf4459c17c5e70e7b8bc718c9fc5ad4ba5eb4f5fd27396f740"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.3"
|
|
||||||
shelf:
|
shelf:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -827,18 +811,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.1"
|
version: "1.12.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.4"
|
||||||
stream_transform:
|
stream_transform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -895,14 +879,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
unorm_dart:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: unorm_dart
|
|
||||||
sha256: "0c69186b03ca6addab0774bcc0f4f17b88d4ce78d9d4d8f0619e30a99ead58e7"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.2"
|
|
||||||
uuid:
|
uuid:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ dependencies:
|
||||||
path: ../mnemo_cards_common_backend
|
path: ../mnemo_cards_common_backend
|
||||||
|
|
||||||
# PostgreSQL + Drift
|
# PostgreSQL + Drift
|
||||||
drift: ^2.14.0
|
drift: ^2.30.0
|
||||||
drift_postgres: ^1.1.0
|
drift_postgres: ^1.3.1
|
||||||
postgres: ^3.0.4
|
postgres: ^3.5.9
|
||||||
|
|
||||||
image: ^4.1.7
|
image: ^4.1.7
|
||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
|
|
@ -61,7 +61,7 @@ dev_dependencies:
|
||||||
copy_with_extension_gen: ^11.0.0
|
copy_with_extension_gen: ^11.0.0
|
||||||
|
|
||||||
# Drift code generation
|
# Drift code generation
|
||||||
drift_dev: ^2.14.0
|
drift_dev: ^2.30.0
|
||||||
|
|
||||||
shelf_router_generator: ^1.1.3
|
shelf_router_generator: ^1.1.3
|
||||||
shelf_open_api_generator:
|
shelf_open_api_generator:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue