@studious-lms/server 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +99 -4
- package/dist/lib/fileUpload.d.ts +5 -0
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +19 -3
- package/dist/lib/googleCloudStorage.d.ts +2 -1
- package/dist/lib/googleCloudStorage.d.ts.map +1 -1
- package/dist/lib/googleCloudStorage.js +12 -5
- package/dist/routers/_app.d.ts +238 -22
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +79 -0
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +39 -1
- package/dist/routers/folder.d.ts +1 -0
- package/dist/routers/folder.d.ts.map +1 -1
- package/dist/routers/folder.js +1 -0
- package/dist/routers/user.d.ts +39 -11
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +185 -25
- package/package.json +1 -1
- package/prisma/migrations/20250920143543_add_profile_fields/migration.sql +15 -0
- package/prisma/schema.prisma +10 -1
- package/src/index.ts +111 -4
- package/src/lib/fileUpload.ts +30 -4
- package/src/lib/googleCloudStorage.ts +14 -5
- package/src/routers/assignment.ts +43 -1
- package/src/routers/folder.ts +1 -0
- package/src/routers/user.ts +200 -25
package/dist/index.js
CHANGED
|
@@ -8,13 +8,56 @@ import { appRouter } from './routers/_app.js';
|
|
|
8
8
|
import { createTRPCContext, createCallerFactory } from './trpc.js';
|
|
9
9
|
import { logger } from './utils/logger.js';
|
|
10
10
|
import { setupSocketHandlers } from './socket/handlers.js';
|
|
11
|
+
import { bucket } from './lib/googleCloudStorage.js';
|
|
11
12
|
dotenv.config();
|
|
12
13
|
const app = express();
|
|
13
14
|
// CORS middleware
|
|
14
15
|
app.use(cors({
|
|
15
|
-
origin: [
|
|
16
|
+
origin: [
|
|
17
|
+
'http://localhost:3000', // Frontend development server
|
|
18
|
+
'http://localhost:3001', // Server port
|
|
19
|
+
'http://127.0.0.1:3000', // Alternative localhost
|
|
20
|
+
'http://127.0.0.1:3001', // Alternative localhost
|
|
21
|
+
process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
|
22
|
+
],
|
|
16
23
|
credentials: true,
|
|
24
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
25
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'x-user'],
|
|
26
|
+
optionsSuccessStatus: 200
|
|
17
27
|
}));
|
|
28
|
+
// Handle preflight OPTIONS requests
|
|
29
|
+
app.options('*', (req, res) => {
|
|
30
|
+
const allowedOrigins = [
|
|
31
|
+
'http://localhost:3000',
|
|
32
|
+
'http://localhost:3001',
|
|
33
|
+
'http://127.0.0.1:3000',
|
|
34
|
+
'http://127.0.0.1:3001',
|
|
35
|
+
process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
|
36
|
+
];
|
|
37
|
+
const origin = req.headers.origin;
|
|
38
|
+
if (origin && allowedOrigins.includes(origin)) {
|
|
39
|
+
res.header('Access-Control-Allow-Origin', origin);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
|
|
43
|
+
}
|
|
44
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
45
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, x-user');
|
|
46
|
+
res.header('Access-Control-Allow-Credentials', 'true');
|
|
47
|
+
res.sendStatus(200);
|
|
48
|
+
});
|
|
49
|
+
// CORS debugging middleware
|
|
50
|
+
app.use((req, res, next) => {
|
|
51
|
+
if (req.method === 'OPTIONS' || req.path.includes('trpc')) {
|
|
52
|
+
logger.info('CORS Request', {
|
|
53
|
+
method: req.method,
|
|
54
|
+
path: req.path,
|
|
55
|
+
origin: req.headers.origin,
|
|
56
|
+
userAgent: req.headers['user-agent']
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
next();
|
|
60
|
+
});
|
|
18
61
|
// Response time logging middleware
|
|
19
62
|
app.use((req, res, next) => {
|
|
20
63
|
const start = Date.now();
|
|
@@ -34,10 +77,16 @@ const httpServer = createServer(app);
|
|
|
34
77
|
// Setup Socket.IO
|
|
35
78
|
const io = new Server(httpServer, {
|
|
36
79
|
cors: {
|
|
37
|
-
origin:
|
|
38
|
-
|
|
80
|
+
origin: [
|
|
81
|
+
'http://localhost:3000', // Frontend development server
|
|
82
|
+
'http://localhost:3001', // Server port
|
|
83
|
+
'http://127.0.0.1:3000', // Alternative localhost
|
|
84
|
+
'http://127.0.0.1:3001', // Alternative localhost
|
|
85
|
+
process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
|
86
|
+
],
|
|
87
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
39
88
|
credentials: true,
|
|
40
|
-
allowedHeaders: ['Access-Control-Allow-Origin']
|
|
89
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Access-Control-Allow-Origin', 'x-user']
|
|
41
90
|
},
|
|
42
91
|
transports: ['websocket', 'polling'],
|
|
43
92
|
pingTimeout: 60000,
|
|
@@ -52,6 +101,42 @@ io.engine.on('connection_error', (err) => {
|
|
|
52
101
|
});
|
|
53
102
|
// Setup socket handlers
|
|
54
103
|
setupSocketHandlers(io);
|
|
104
|
+
// File serving endpoint for secure file access
|
|
105
|
+
app.get('/api/files/:filePath', async (req, res) => {
|
|
106
|
+
try {
|
|
107
|
+
const filePath = decodeURIComponent(req.params.filePath);
|
|
108
|
+
console.log('File request:', { filePath, originalPath: req.params.filePath });
|
|
109
|
+
// Get file from Google Cloud Storage
|
|
110
|
+
const file = bucket.file(filePath);
|
|
111
|
+
const [exists] = await file.exists();
|
|
112
|
+
console.log('File exists:', exists, 'for path:', filePath);
|
|
113
|
+
if (!exists) {
|
|
114
|
+
return res.status(404).json({ error: 'File not found', filePath });
|
|
115
|
+
}
|
|
116
|
+
// Get file metadata
|
|
117
|
+
const [metadata] = await file.getMetadata();
|
|
118
|
+
// Set appropriate headers
|
|
119
|
+
res.set({
|
|
120
|
+
'Content-Type': metadata.contentType || 'application/octet-stream',
|
|
121
|
+
'Content-Length': metadata.size,
|
|
122
|
+
'Cache-Control': 'public, max-age=31536000', // 1 year cache
|
|
123
|
+
'ETag': metadata.etag,
|
|
124
|
+
});
|
|
125
|
+
// Stream file to response
|
|
126
|
+
const stream = file.createReadStream();
|
|
127
|
+
stream.pipe(res);
|
|
128
|
+
stream.on('error', (error) => {
|
|
129
|
+
console.error('Error streaming file:', error);
|
|
130
|
+
if (!res.headersSent) {
|
|
131
|
+
res.status(500).json({ error: 'Error streaming file' });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error('Error serving file:', error);
|
|
137
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
55
140
|
// Create caller
|
|
56
141
|
const createCaller = createCallerFactory(appRouter);
|
|
57
142
|
// Setup tRPC middleware
|
|
@@ -75,3 +160,13 @@ logger.info('Configurations', {
|
|
|
75
160
|
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
|
76
161
|
LOG_MODE: process.env.LOG_MODE,
|
|
77
162
|
});
|
|
163
|
+
// Log CORS configuration
|
|
164
|
+
logger.info('CORS Configuration', {
|
|
165
|
+
allowedOrigins: [
|
|
166
|
+
'http://localhost:3000',
|
|
167
|
+
'http://localhost:3001',
|
|
168
|
+
'http://127.0.0.1:3000',
|
|
169
|
+
'http://127.0.0.1:3001',
|
|
170
|
+
process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
|
171
|
+
]
|
|
172
|
+
});
|
package/dist/lib/fileUpload.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileUpload.d.ts","sourceRoot":"","sources":["../../src/lib/fileUpload.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"fileUpload.d.ts","sourceRoot":"","sources":["../../src/lib/fileUpload.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAEd;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,YAAY,CAAC,CAkHvB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAWzB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUlE"}
|
package/dist/lib/fileUpload.js
CHANGED
|
@@ -13,8 +13,22 @@ import { prisma } from "./prisma.js";
|
|
|
13
13
|
*/
|
|
14
14
|
export async function uploadFile(file, userId, directory, assignmentId) {
|
|
15
15
|
try {
|
|
16
|
+
// Validate file extension matches MIME type
|
|
17
|
+
const fileExtension = file.name.split('.').pop()?.toLowerCase();
|
|
18
|
+
const mimeType = file.type.toLowerCase();
|
|
19
|
+
const extensionMimeMap = {
|
|
20
|
+
'jpg': ['image/jpeg'],
|
|
21
|
+
'jpeg': ['image/jpeg'],
|
|
22
|
+
'png': ['image/png'],
|
|
23
|
+
'gif': ['image/gif'],
|
|
24
|
+
'webp': ['image/webp']
|
|
25
|
+
};
|
|
26
|
+
if (fileExtension && extensionMimeMap[fileExtension]) {
|
|
27
|
+
if (!extensionMimeMap[fileExtension].includes(mimeType)) {
|
|
28
|
+
throw new Error(`File extension .${fileExtension} does not match MIME type ${mimeType}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
16
31
|
// Create a unique filename
|
|
17
|
-
const fileExtension = file.name.split('.').pop();
|
|
18
32
|
const uniqueFilename = `${uuidv4()}.${fileExtension}`;
|
|
19
33
|
// // Construct the full path
|
|
20
34
|
const filePath = directory
|
|
@@ -25,8 +39,9 @@ export async function uploadFile(file, userId, directory, assignmentId) {
|
|
|
25
39
|
// // Generate and store thumbnail if supported
|
|
26
40
|
let thumbnailId;
|
|
27
41
|
try {
|
|
28
|
-
//
|
|
29
|
-
|
|
42
|
+
// // Convert base64 to buffer for thumbnail generation
|
|
43
|
+
// Handle both data URI format (data:image/jpeg;base64,...) and raw base64
|
|
44
|
+
const base64Data = file.data.includes(',') ? file.data.split(',')[1] : file.data;
|
|
30
45
|
const fileBuffer = Buffer.from(base64Data, 'base64');
|
|
31
46
|
// // Generate thumbnail directly from buffer
|
|
32
47
|
const thumbnailBuffer = await generateMediaThumbnail(fileBuffer, file.type);
|
|
@@ -52,6 +67,7 @@ export async function uploadFile(file, userId, directory, assignmentId) {
|
|
|
52
67
|
}
|
|
53
68
|
catch (error) {
|
|
54
69
|
console.warn('Failed to generate thumbnail:', error);
|
|
70
|
+
// Continue without thumbnail - this is not a critical failure
|
|
55
71
|
}
|
|
56
72
|
// Create file record in database
|
|
57
73
|
// const uploadedPath = '/dummyPath' + Math.random().toString(36).substring(2, 15);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const bucket: import("@google-cloud/storage").Bucket;
|
|
1
2
|
/**
|
|
2
3
|
* Uploads a file to Google Cloud Storage
|
|
3
4
|
* @param base64Data Base64 encoded file data
|
|
@@ -11,7 +12,7 @@ export declare function uploadFile(base64Data: string, filePath: string, content
|
|
|
11
12
|
* @param filePath The path of the file in the bucket
|
|
12
13
|
* @returns The signed URL
|
|
13
14
|
*/
|
|
14
|
-
export declare function getSignedUrl(filePath: string): Promise<string>;
|
|
15
|
+
export declare function getSignedUrl(filePath: string, action?: 'read' | 'write', contentType?: string): Promise<string>;
|
|
15
16
|
/**
|
|
16
17
|
* Deletes a file from Google Cloud Storage
|
|
17
18
|
* @param filePath The path of the file to delete
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"googleCloudStorage.d.ts","sourceRoot":"","sources":["../../src/lib/googleCloudStorage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"googleCloudStorage.d.ts","sourceRoot":"","sources":["../../src/lib/googleCloudStorage.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,MAAM,wCAAwD,CAAC;AAK5E;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,GAAG,OAAgB,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB7H;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhE"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
dotenv.config();
|
|
1
3
|
import { Storage } from '@google-cloud/storage';
|
|
2
4
|
import { TRPCError } from '@trpc/server';
|
|
3
5
|
const storage = new Storage({
|
|
@@ -7,7 +9,7 @@ const storage = new Storage({
|
|
|
7
9
|
private_key: process.env.GOOGLE_CLOUD_PRIVATE_KEY?.replace(/\\n/g, '\n'),
|
|
8
10
|
},
|
|
9
11
|
});
|
|
10
|
-
const bucket = storage.bucket(process.env.GOOGLE_CLOUD_BUCKET_NAME
|
|
12
|
+
export const bucket = storage.bucket(process.env.GOOGLE_CLOUD_BUCKET_NAME);
|
|
11
13
|
// Short expiration time for signed URLs (5 minutes)
|
|
12
14
|
const SIGNED_URL_EXPIRATION = 5 * 60 * 1000;
|
|
13
15
|
/**
|
|
@@ -48,13 +50,18 @@ export async function uploadFile(base64Data, filePath, contentType) {
|
|
|
48
50
|
* @param filePath The path of the file in the bucket
|
|
49
51
|
* @returns The signed URL
|
|
50
52
|
*/
|
|
51
|
-
export async function getSignedUrl(filePath) {
|
|
53
|
+
export async function getSignedUrl(filePath, action = 'read', contentType) {
|
|
52
54
|
try {
|
|
53
|
-
const
|
|
55
|
+
const options = {
|
|
54
56
|
version: 'v4',
|
|
55
|
-
action:
|
|
57
|
+
action: action,
|
|
56
58
|
expires: Date.now() + SIGNED_URL_EXPIRATION,
|
|
57
|
-
}
|
|
59
|
+
};
|
|
60
|
+
// For write operations, add content type if provided
|
|
61
|
+
if (action === 'write' && contentType) {
|
|
62
|
+
options.contentType = contentType;
|
|
63
|
+
}
|
|
64
|
+
const [url] = await bucket.file(filePath).getSignedUrl(options);
|
|
58
65
|
return url;
|
|
59
66
|
}
|
|
60
67
|
catch (error) {
|
package/dist/routers/_app.d.ts
CHANGED
|
@@ -1597,6 +1597,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
1597
1597
|
classId: string;
|
|
1598
1598
|
assignmentId: string;
|
|
1599
1599
|
submissionId: string;
|
|
1600
|
+
feedback?: string | undefined;
|
|
1600
1601
|
gradeReceived?: number | null | undefined;
|
|
1601
1602
|
existingFileIds?: string[] | undefined;
|
|
1602
1603
|
removedAttachments?: string[] | undefined;
|
|
@@ -2061,6 +2062,84 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
2061
2062
|
};
|
|
2062
2063
|
meta: object;
|
|
2063
2064
|
}>;
|
|
2065
|
+
detachGradingBoundary: import("@trpc/server").TRPCMutationProcedure<{
|
|
2066
|
+
input: {
|
|
2067
|
+
[x: string]: unknown;
|
|
2068
|
+
classId: string;
|
|
2069
|
+
assignmentId: string;
|
|
2070
|
+
};
|
|
2071
|
+
output: {
|
|
2072
|
+
section: {
|
|
2073
|
+
id: string;
|
|
2074
|
+
name: string;
|
|
2075
|
+
color: string | null;
|
|
2076
|
+
classId: string;
|
|
2077
|
+
order: number | null;
|
|
2078
|
+
} | null;
|
|
2079
|
+
teacher: {
|
|
2080
|
+
id: string;
|
|
2081
|
+
username: string;
|
|
2082
|
+
email: string;
|
|
2083
|
+
password: string;
|
|
2084
|
+
verified: boolean;
|
|
2085
|
+
role: import(".prisma/client").$Enums.UserRole;
|
|
2086
|
+
profileId: string | null;
|
|
2087
|
+
schoolId: string | null;
|
|
2088
|
+
};
|
|
2089
|
+
attachments: {
|
|
2090
|
+
path: string;
|
|
2091
|
+
type: string;
|
|
2092
|
+
id: string;
|
|
2093
|
+
name: string;
|
|
2094
|
+
size: number | null;
|
|
2095
|
+
uploadedAt: Date | null;
|
|
2096
|
+
assignmentId: string | null;
|
|
2097
|
+
submissionId: string | null;
|
|
2098
|
+
userId: string | null;
|
|
2099
|
+
thumbnailId: string | null;
|
|
2100
|
+
annotationId: string | null;
|
|
2101
|
+
classDraftId: string | null;
|
|
2102
|
+
folderId: string | null;
|
|
2103
|
+
}[];
|
|
2104
|
+
eventAttached: {
|
|
2105
|
+
id: string;
|
|
2106
|
+
name: string | null;
|
|
2107
|
+
color: string | null;
|
|
2108
|
+
location: string | null;
|
|
2109
|
+
startTime: Date;
|
|
2110
|
+
endTime: Date;
|
|
2111
|
+
remarks: string | null;
|
|
2112
|
+
classId: string | null;
|
|
2113
|
+
userId: string | null;
|
|
2114
|
+
} | null;
|
|
2115
|
+
gradingBoundary: {
|
|
2116
|
+
id: string;
|
|
2117
|
+
classId: string;
|
|
2118
|
+
structured: string;
|
|
2119
|
+
} | null;
|
|
2120
|
+
} & {
|
|
2121
|
+
type: import(".prisma/client").$Enums.AssignmentType;
|
|
2122
|
+
id: string;
|
|
2123
|
+
title: string;
|
|
2124
|
+
dueDate: Date;
|
|
2125
|
+
maxGrade: number | null;
|
|
2126
|
+
classId: string;
|
|
2127
|
+
eventId: string | null;
|
|
2128
|
+
markSchemeId: string | null;
|
|
2129
|
+
gradingBoundaryId: string | null;
|
|
2130
|
+
instructions: string;
|
|
2131
|
+
weight: number;
|
|
2132
|
+
graded: boolean;
|
|
2133
|
+
sectionId: string | null;
|
|
2134
|
+
template: boolean;
|
|
2135
|
+
createdAt: Date | null;
|
|
2136
|
+
modifiedAt: Date | null;
|
|
2137
|
+
teacherId: string;
|
|
2138
|
+
inProgress: boolean;
|
|
2139
|
+
order: number | null;
|
|
2140
|
+
};
|
|
2141
|
+
meta: object;
|
|
2142
|
+
}>;
|
|
2064
2143
|
}>>;
|
|
2065
2144
|
user: import("@trpc/server").TRPCBuiltRouter<{
|
|
2066
2145
|
ctx: import("../trpc.js").Context;
|
|
@@ -2085,29 +2164,57 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
2085
2164
|
id: string;
|
|
2086
2165
|
username: string;
|
|
2087
2166
|
profile: {
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2167
|
+
displayName: any;
|
|
2168
|
+
bio: any;
|
|
2169
|
+
location: any;
|
|
2170
|
+
website: any;
|
|
2171
|
+
profilePicture: string | null;
|
|
2172
|
+
profilePictureThumbnail: string | null;
|
|
2173
|
+
};
|
|
2091
2174
|
};
|
|
2092
2175
|
meta: object;
|
|
2093
2176
|
}>;
|
|
2094
2177
|
updateProfile: import("@trpc/server").TRPCMutationProcedure<{
|
|
2095
2178
|
input: {
|
|
2096
|
-
profile
|
|
2179
|
+
profile?: {
|
|
2180
|
+
location?: string | null | undefined;
|
|
2181
|
+
displayName?: string | null | undefined;
|
|
2182
|
+
bio?: string | null | undefined;
|
|
2183
|
+
website?: string | null | undefined;
|
|
2184
|
+
} | undefined;
|
|
2097
2185
|
profilePicture?: {
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2186
|
+
filePath: string;
|
|
2187
|
+
fileName: string;
|
|
2188
|
+
fileType: string;
|
|
2189
|
+
fileSize: number;
|
|
2190
|
+
} | undefined;
|
|
2191
|
+
dicebearAvatar?: {
|
|
2192
|
+
url: string;
|
|
2102
2193
|
} | undefined;
|
|
2103
2194
|
};
|
|
2104
2195
|
output: {
|
|
2105
2196
|
id: string;
|
|
2106
2197
|
username: string;
|
|
2107
2198
|
profile: {
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2199
|
+
displayName: any;
|
|
2200
|
+
bio: any;
|
|
2201
|
+
location: any;
|
|
2202
|
+
website: any;
|
|
2203
|
+
profilePicture: string | null;
|
|
2204
|
+
profilePictureThumbnail: string | null;
|
|
2205
|
+
};
|
|
2206
|
+
};
|
|
2207
|
+
meta: object;
|
|
2208
|
+
}>;
|
|
2209
|
+
getUploadUrl: import("@trpc/server").TRPCMutationProcedure<{
|
|
2210
|
+
input: {
|
|
2211
|
+
fileName: string;
|
|
2212
|
+
fileType: string;
|
|
2213
|
+
};
|
|
2214
|
+
output: {
|
|
2215
|
+
uploadUrl: string;
|
|
2216
|
+
filePath: string;
|
|
2217
|
+
fileName: string;
|
|
2111
2218
|
};
|
|
2112
2219
|
meta: object;
|
|
2113
2220
|
}>;
|
|
@@ -2884,6 +2991,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
2884
2991
|
childFolders: number;
|
|
2885
2992
|
};
|
|
2886
2993
|
name: string;
|
|
2994
|
+
color: string | null;
|
|
2887
2995
|
}[];
|
|
2888
2996
|
parentFolder: {
|
|
2889
2997
|
id: string;
|
|
@@ -4813,6 +4921,7 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
|
|
|
4813
4921
|
classId: string;
|
|
4814
4922
|
assignmentId: string;
|
|
4815
4923
|
submissionId: string;
|
|
4924
|
+
feedback?: string | undefined;
|
|
4816
4925
|
gradeReceived?: number | null | undefined;
|
|
4817
4926
|
existingFileIds?: string[] | undefined;
|
|
4818
4927
|
removedAttachments?: string[] | undefined;
|
|
@@ -5277,6 +5386,84 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
|
|
|
5277
5386
|
};
|
|
5278
5387
|
meta: object;
|
|
5279
5388
|
}>;
|
|
5389
|
+
detachGradingBoundary: import("@trpc/server").TRPCMutationProcedure<{
|
|
5390
|
+
input: {
|
|
5391
|
+
[x: string]: unknown;
|
|
5392
|
+
classId: string;
|
|
5393
|
+
assignmentId: string;
|
|
5394
|
+
};
|
|
5395
|
+
output: {
|
|
5396
|
+
section: {
|
|
5397
|
+
id: string;
|
|
5398
|
+
name: string;
|
|
5399
|
+
color: string | null;
|
|
5400
|
+
classId: string;
|
|
5401
|
+
order: number | null;
|
|
5402
|
+
} | null;
|
|
5403
|
+
teacher: {
|
|
5404
|
+
id: string;
|
|
5405
|
+
username: string;
|
|
5406
|
+
email: string;
|
|
5407
|
+
password: string;
|
|
5408
|
+
verified: boolean;
|
|
5409
|
+
role: import(".prisma/client").$Enums.UserRole;
|
|
5410
|
+
profileId: string | null;
|
|
5411
|
+
schoolId: string | null;
|
|
5412
|
+
};
|
|
5413
|
+
attachments: {
|
|
5414
|
+
path: string;
|
|
5415
|
+
type: string;
|
|
5416
|
+
id: string;
|
|
5417
|
+
name: string;
|
|
5418
|
+
size: number | null;
|
|
5419
|
+
uploadedAt: Date | null;
|
|
5420
|
+
assignmentId: string | null;
|
|
5421
|
+
submissionId: string | null;
|
|
5422
|
+
userId: string | null;
|
|
5423
|
+
thumbnailId: string | null;
|
|
5424
|
+
annotationId: string | null;
|
|
5425
|
+
classDraftId: string | null;
|
|
5426
|
+
folderId: string | null;
|
|
5427
|
+
}[];
|
|
5428
|
+
eventAttached: {
|
|
5429
|
+
id: string;
|
|
5430
|
+
name: string | null;
|
|
5431
|
+
color: string | null;
|
|
5432
|
+
location: string | null;
|
|
5433
|
+
startTime: Date;
|
|
5434
|
+
endTime: Date;
|
|
5435
|
+
remarks: string | null;
|
|
5436
|
+
classId: string | null;
|
|
5437
|
+
userId: string | null;
|
|
5438
|
+
} | null;
|
|
5439
|
+
gradingBoundary: {
|
|
5440
|
+
id: string;
|
|
5441
|
+
classId: string;
|
|
5442
|
+
structured: string;
|
|
5443
|
+
} | null;
|
|
5444
|
+
} & {
|
|
5445
|
+
type: import(".prisma/client").$Enums.AssignmentType;
|
|
5446
|
+
id: string;
|
|
5447
|
+
title: string;
|
|
5448
|
+
dueDate: Date;
|
|
5449
|
+
maxGrade: number | null;
|
|
5450
|
+
classId: string;
|
|
5451
|
+
eventId: string | null;
|
|
5452
|
+
markSchemeId: string | null;
|
|
5453
|
+
gradingBoundaryId: string | null;
|
|
5454
|
+
instructions: string;
|
|
5455
|
+
weight: number;
|
|
5456
|
+
graded: boolean;
|
|
5457
|
+
sectionId: string | null;
|
|
5458
|
+
template: boolean;
|
|
5459
|
+
createdAt: Date | null;
|
|
5460
|
+
modifiedAt: Date | null;
|
|
5461
|
+
teacherId: string;
|
|
5462
|
+
inProgress: boolean;
|
|
5463
|
+
order: number | null;
|
|
5464
|
+
};
|
|
5465
|
+
meta: object;
|
|
5466
|
+
}>;
|
|
5280
5467
|
}>>;
|
|
5281
5468
|
user: import("@trpc/server").TRPCBuiltRouter<{
|
|
5282
5469
|
ctx: import("../trpc.js").Context;
|
|
@@ -5301,29 +5488,57 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
|
|
|
5301
5488
|
id: string;
|
|
5302
5489
|
username: string;
|
|
5303
5490
|
profile: {
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5491
|
+
displayName: any;
|
|
5492
|
+
bio: any;
|
|
5493
|
+
location: any;
|
|
5494
|
+
website: any;
|
|
5495
|
+
profilePicture: string | null;
|
|
5496
|
+
profilePictureThumbnail: string | null;
|
|
5497
|
+
};
|
|
5307
5498
|
};
|
|
5308
5499
|
meta: object;
|
|
5309
5500
|
}>;
|
|
5310
5501
|
updateProfile: import("@trpc/server").TRPCMutationProcedure<{
|
|
5311
5502
|
input: {
|
|
5312
|
-
profile
|
|
5503
|
+
profile?: {
|
|
5504
|
+
location?: string | null | undefined;
|
|
5505
|
+
displayName?: string | null | undefined;
|
|
5506
|
+
bio?: string | null | undefined;
|
|
5507
|
+
website?: string | null | undefined;
|
|
5508
|
+
} | undefined;
|
|
5313
5509
|
profilePicture?: {
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5510
|
+
filePath: string;
|
|
5511
|
+
fileName: string;
|
|
5512
|
+
fileType: string;
|
|
5513
|
+
fileSize: number;
|
|
5514
|
+
} | undefined;
|
|
5515
|
+
dicebearAvatar?: {
|
|
5516
|
+
url: string;
|
|
5318
5517
|
} | undefined;
|
|
5319
5518
|
};
|
|
5320
5519
|
output: {
|
|
5321
5520
|
id: string;
|
|
5322
5521
|
username: string;
|
|
5323
5522
|
profile: {
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5523
|
+
displayName: any;
|
|
5524
|
+
bio: any;
|
|
5525
|
+
location: any;
|
|
5526
|
+
website: any;
|
|
5527
|
+
profilePicture: string | null;
|
|
5528
|
+
profilePictureThumbnail: string | null;
|
|
5529
|
+
};
|
|
5530
|
+
};
|
|
5531
|
+
meta: object;
|
|
5532
|
+
}>;
|
|
5533
|
+
getUploadUrl: import("@trpc/server").TRPCMutationProcedure<{
|
|
5534
|
+
input: {
|
|
5535
|
+
fileName: string;
|
|
5536
|
+
fileType: string;
|
|
5537
|
+
};
|
|
5538
|
+
output: {
|
|
5539
|
+
uploadUrl: string;
|
|
5540
|
+
filePath: string;
|
|
5541
|
+
fileName: string;
|
|
5327
5542
|
};
|
|
5328
5543
|
meta: object;
|
|
5329
5544
|
}>;
|
|
@@ -6100,6 +6315,7 @@ export declare const createCaller: import("@trpc/server").TRPCRouterCaller<{
|
|
|
6100
6315
|
childFolders: number;
|
|
6101
6316
|
};
|
|
6102
6317
|
name: string;
|
|
6318
|
+
color: string | null;
|
|
6103
6319
|
}[];
|
|
6104
6320
|
parentFolder: {
|
|
6105
6321
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_app.d.ts","sourceRoot":"","sources":["../../src/routers/_app.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAU1E,eAAO,MAAM,SAAS
|
|
1
|
+
{"version":3,"file":"_app.d.ts","sourceRoot":"","sources":["../../src/routers/_app.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAU1E,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAapB,CAAC;AAGH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC;AACzC,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;AAG1D,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAAiC,CAAC"}
|