InitController.cs 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. using Azure.Cosmos;
  2. using Azure.Storage.Blobs.Models;
  3. using Azure.Storage.Sas;
  4. using Microsoft.AspNetCore.Http;
  5. using Microsoft.AspNetCore.Mvc;
  6. using Microsoft.Extensions.Options;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Dynamic;
  10. using System.IdentityModel.Tokens.Jwt;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Text.Json;
  14. using System.Threading.Tasks;
  15. using TEAMModelOS.Models;
  16. using TEAMModelOS.SDK.Models;
  17. using TEAMModelOS.SDK.DI;
  18. using TEAMModelOS.SDK.Extension;
  19. using TEAMModelOS.Filter;
  20. using TEAMModelOS.SDK.Models.Cosmos;
  21. using HTEXLib.COMM.Helpers;
  22. using TEAMModelOS.SDK.Models.Service;
  23. using Microsoft.Extensions.Configuration;
  24. using System.Net.Http;
  25. namespace TEAMModelOS.Controllers
  26. {
  27. [ProducesResponseType(StatusCodes.Status200OK)]
  28. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  29. //[Authorize(Roles = "IES5")]
  30. [Route("teacher/init")]
  31. [ApiController]
  32. public class InitController : ControllerBase
  33. {
  34. private readonly AzureCosmosFactory _azureCosmos;
  35. private readonly AzureStorageFactory _azureStorage;
  36. private readonly DingDing _dingDing;
  37. private readonly Option _option;
  38. private readonly IConfiguration _configuration;
  39. private readonly NotificationService _notificationService;
  40. public InitController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, DingDing dingDing, IOptionsSnapshot<Option> option, IConfiguration configuration, NotificationService notificationService)
  41. {
  42. _azureCosmos = azureCosmos;
  43. _azureStorage = azureStorage;
  44. _dingDing = dingDing;
  45. _option = option?.Value;
  46. _configuration = configuration;
  47. _notificationService = notificationService;
  48. }
  49. /// <summary>
  50. /// 修改教师信息
  51. /// </summary>
  52. /// <param name="request"></param>
  53. /// <returns></returns>
  54. [ProducesDefaultResponseType]
  55. [HttpPost("set-teacher-info")]
  56. [AuthToken(Roles = "admin,teacher,student")]
  57. public async Task<IActionResult> SetTeacherInfo(JsonElement request) {
  58. var client = _azureCosmos.GetCosmosClient();
  59. if (!request.TryGetProperty("defaultSchool", out JsonElement _defaultSchool))
  60. {
  61. return BadRequest();
  62. }
  63. if (!request.TryGetProperty("opt", out JsonElement _opt))
  64. {
  65. return BadRequest();
  66. }
  67. var (userid, _, _, _) = HttpContext.GetAuthTokenInfo();
  68. try {
  69. Teacher teacher = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<Teacher>(userid, new PartitionKey("Base"));
  70. switch (true) {
  71. //修改默认学校
  72. case bool when $"{_opt}".Equals("UpdateDefaultSchool", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty($"{_defaultSchool}"):
  73. if (teacher.schools.Select(x => x.schoolId).Contains($"{_defaultSchool}"))
  74. {
  75. teacher.defaultSchool = $"{_defaultSchool}";
  76. await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<Teacher>(teacher, userid, new PartitionKey("Base"));
  77. return Ok(new { teacher, status = 1 });
  78. }
  79. else {
  80. return BadRequest(new { status=-1,msg="您未加入该学校!"});
  81. }
  82. }
  83. }
  84. catch(Exception ex)
  85. {
  86. return BadRequest("teacher not exist");
  87. }
  88. return Ok();
  89. }
  90. //TODO 此API需處理對應前端返回的相關數據
  91. [ProducesDefaultResponseType]
  92. [HttpPost("get-teacher-info")]
  93. public async Task<IActionResult> GetTeacherInfo(JsonElement request)
  94. {
  95. try
  96. {
  97. if (!request.TryGetProperty("id_token", out JsonElement id_token)) return BadRequest();
  98. var jwt = new JwtSecurityToken(id_token.GetString());
  99. //TODO 此驗證IdToken先簡單檢查,後面需向Core ID新API,驗證Token
  100. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.OrdinalIgnoreCase)) return BadRequest();
  101. var id = jwt.Payload.Sub;
  102. jwt.Payload.TryGetValue("name", out object name);
  103. jwt.Payload.TryGetValue("picture", out object picture);
  104. List<object> schools = new List<object>();
  105. string defaultschool = null;
  106. //TODO 取得Teacher 個人相關數據(課程清單、虛擬教室清單、歷史紀錄清單等),學校數據另外API處理,多校切換時不同
  107. var client = _azureCosmos.GetCosmosClient();
  108. int total = 0;
  109. int tsize = 0;
  110. try {
  111. Teacher teacher = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<Teacher>(id, new PartitionKey("Base"));
  112. teacher.name = $"{name}";
  113. teacher.picture = $"{picture}";
  114. ///教师的个人空间
  115. tsize = teacher.size;
  116. ///教师的总空间 包含 个人空间和学校赠送的空间累加
  117. total = teacher.size;
  118. //检查是否有加入学校,如果加入学校,则当个人空间size是0G的时候,则免费获得一个G空间,但无论加入多少个学校,只能获取一次 1G的免费空间。没有加入学校则默认0G空间,除非自己购买空间
  119. if (teacher.schools.IsNotEmpty()) {
  120. foreach (var sc in teacher.schools) {
  121. string statusNow = sc.status!=null?sc.status:"";
  122. if (statusNow .Equals("join") || statusNow .Equals("invite") || statusNow.Equals("request")) {
  123. dynamic schoolExtobj = new ExpandoObject();
  124. var schoolJson = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync($"{sc.schoolId}", new PartitionKey("Base"));
  125. var school = await JsonDocument.ParseAsync(schoolJson.ContentStream);
  126. schoolExtobj.schoolId = sc.schoolId;
  127. schoolExtobj.name = school.RootElement.GetProperty("name") ;
  128. schoolExtobj.status =sc.status;
  129. schoolExtobj.time = sc.time;
  130. schoolExtobj.picture = school.RootElement.GetProperty("picture");
  131. var sctch = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(id, new PartitionKey($"Teacher-{sc.schoolId}"));
  132. if (sctch.Status == 200)
  133. {
  134. var jsonDoc = await JsonDocument.ParseAsync(sctch.ContentStream);
  135. SchoolTeacher schoolTeacher = jsonDoc.RootElement.ToObject<SchoolTeacher>();
  136. schoolTeacher.name = $"{name}";
  137. schoolTeacher.picture = $"{picture}";
  138. await client.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync(schoolTeacher, id, new PartitionKey($"Teacher-{sc.schoolId}"));
  139. if (jsonDoc.RootElement.TryGetProperty("size", out JsonElement _size) && _size.ValueKind.Equals(JsonValueKind.Number))
  140. {
  141. total += _size.GetInt32();
  142. schoolExtobj.size = _size.GetInt32();
  143. }
  144. else { schoolExtobj.size = 0; }
  145. }
  146. else {
  147. schoolExtobj.size=0;
  148. }
  149. schools.Add(schoolExtobj);
  150. }
  151. }
  152. //如果包含任何申请,邀请,加入学校的记录 且个人空间未分配2G 则默认分配个人空间至2G.
  153. if ( teacher.size<2 && teacher.schools.Count > 0)
  154. {
  155. teacher.size = 2;
  156. }
  157. //如果未包含任何申请,邀请,加入学校的记录 且 个人空间没有分配1G 则默认赠送一个G
  158. if (teacher.schools.Count == 0 && teacher.size<1) {
  159. teacher.size = 1;
  160. }
  161. }
  162. await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<Teacher>(teacher, id, new PartitionKey("Base"));
  163. //預設學校ID
  164. defaultschool = teacher.defaultSchool;
  165. } catch(CosmosException ex)
  166. {
  167. if (ex.Status == 404) {
  168. //如果沒有,則初始化Teacher基本資料到Cosmos
  169. Teacher teacher = new Teacher
  170. {
  171. id = id,
  172. pk = "Base",
  173. code = "Base",
  174. name = name?.ToString(),
  175. picture = picture?.ToString(),
  176. //创建账号并第一次登录IES5则默认赠送1G
  177. size = 1,
  178. defaultSchool = null,
  179. schools = new List<Teacher.School>(),
  180. };
  181. var container = _azureStorage.GetBlobContainerClient(id);
  182. await container.CreateIfNotExistsAsync(PublicAccessType.None); //嘗試創建Teacher私有容器,如存在則不做任何事,保障容器一定存在
  183. teacher = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Teacher").CreateItemAsync<Teacher>(teacher, new PartitionKey("Base"));
  184. total = teacher.size;
  185. tsize = teacher.size;
  186. }
  187. }
  188. //私人課程
  189. List<object> courses = new List<object>();
  190. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIterator(queryText: $"select c.id, c.name, c.classes, c.notice ,c.scope from c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Course-{id}") }))
  191. {
  192. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  193. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  194. {
  195. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  196. {
  197. courses.Add(obj.ToObject<object>());
  198. }
  199. }
  200. }
  201. //老師個人課綱 TODO 暂时不取
  202. List<SyllabusRole> syllabus = new List<SyllabusRole>();
  203. //await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.scope, c.resourceCount, c.itemCount, c.children from c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Syllabus-{id}") }))
  204. //{
  205. // var jsons = await JsonDocument.ParseAsync(item.ContentStream);
  206. // if (jsons.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  207. // {
  208. // foreach (var obj in jsons.RootElement.GetProperty("Documents").EnumerateArray())
  209. // {
  210. // SyllabusRole syllabusRole = new SyllabusRole();
  211. // syllabusRole.id = obj.GetProperty("id").ToString();
  212. // syllabusRole.name = obj.GetProperty("name").ToString();
  213. // syllabusRole.resourceCount = obj.GetProperty("resourceCount").GetUInt16();
  214. // syllabusRole.itemCount = obj.GetProperty("itemCount").GetUInt16();
  215. // List<Syllabus> syllabusList = obj.GetProperty("children").ToObject<List<Syllabus>>();
  216. // syllabusList.Insert(0, new Syllabus { id = syllabusRole.id, name = syllabusRole.name, pid = "", order = 0 });
  217. // syllabusList = syllabusList.OrderBy(x => x.order).ToList();
  218. // syllabusRole.structure = CreateSyllabusTree(syllabusList);
  219. // syllabus.Add(syllabusRole);
  220. // }
  221. // //[DEBUG] string jsonString = System.Text.Json.JsonSerializer.Serialize(syllabusRoles);
  222. // }
  223. //}
  224. //換取AuthToken,提供給前端
  225. var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, roles: new[] { "teacher" });
  226. //取得Teacher Blob 容器位置及SAS
  227. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(id, BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete);
  228. return Ok(new { auth_token, blob_uri, blob_sas, schools, defaultschool, courses, total, tsize });
  229. }
  230. catch (CosmosException ex) {
  231. await _dingDing.SendBotMsg($"IES5,{_option.Location},Teacher/GetTeacherInfo()\n{ex.Message}{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  232. return BadRequest();
  233. }
  234. catch (Exception ex)
  235. {
  236. await _dingDing.SendBotMsg($"IES5,{_option.Location},Teacher/GetTeacherInfo()\n{ex.Message}{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  237. return BadRequest();
  238. }
  239. }
  240. //TODO 此API需處理對應前端返回的相關數據
  241. [ProducesDefaultResponseType]
  242. [HttpPost("get-school-info")]
  243. public async Task<IActionResult> GetSchoolInfo(JsonElement requert)
  244. {
  245. try
  246. {
  247. if (!requert.TryGetProperty("id_token", out JsonElement id_token)) return BadRequest();
  248. if (!requert.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  249. var jwt = new JwtSecurityToken(id_token.GetString());
  250. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.Ordinal)) return BadRequest();
  251. var id = jwt.Payload.Sub;
  252. var client = _azureCosmos.GetCosmosClient();
  253. //權限token
  254. jwt.Payload.TryGetValue("name", out object name);
  255. jwt.Payload.TryGetValue("picture", out object picture);
  256. List<string> roles = new List<string>();
  257. List<string> permissions = new List<string>();
  258. int size = 0;
  259. var response = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(id, new PartitionKey($"Teacher-{school_code}"));
  260. if (response.Status == 200)
  261. {
  262. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  263. if (json.RootElement.TryGetProperty("size", out JsonElement _size) && _size.ValueKind == JsonValueKind.Number)
  264. {
  265. size = _size.GetInt32();
  266. }
  267. if (json.RootElement.TryGetProperty("roles", out JsonElement _roles) && _roles.ValueKind != JsonValueKind.Null)
  268. {
  269. foreach (var obj in _roles.EnumerateArray())
  270. {
  271. roles.Add(obj.GetString());
  272. }
  273. }
  274. if (json.RootElement.TryGetProperty("permissions", out JsonElement _permissions) && _permissions.ValueKind != JsonValueKind.Null)
  275. {
  276. foreach (var obj in _permissions.EnumerateArray())
  277. {
  278. permissions.Add(obj.GetString());
  279. }
  280. }
  281. }
  282. else //無此學校資料
  283. {
  284. return Ok(new { status=404});
  285. }
  286. if (roles.Count == 0)
  287. {
  288. roles.Add("teacher");
  289. roles.Add("student");
  290. }
  291. //TODO JJ,更新Token时,在取得学校资讯时,没有传入schoolId
  292. var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, schoolID: school_code.ToString(), roles: roles.ToArray(), permissions: permissions.ToArray());
  293. //TODO JJ,调整为取得学校基础设置数据,取代下方學校學制、年級
  294. object school_base = null;
  295. response = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(school_code.ToString(), new PartitionKey("Base"));
  296. if (response.Status == 200)
  297. {
  298. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  299. school_base = json.RootElement.ToObject<object>();
  300. }
  301. //取得班级
  302. List<object> school_classes = new List<object>();
  303. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id,c.x,c.y,c.name,c.year,c.teacher,c.periodId,c.gradeId,c.room,c.sn,c.no,c.style,c.status,c.openType,c.school, ARRAY_LENGTH(c.students) AS studCount FROM c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
  304. {
  305. var jsonc = await JsonDocument.ParseAsync(item.ContentStream);
  306. foreach (var classeinfo in jsonc.RootElement.GetProperty("Documents").EnumerateArray())
  307. {
  308. school_classes.Add(classeinfo.ToObject<object>());
  309. }
  310. }
  311. //取得教室
  312. List<Room> school_rooms = new List<Room>();
  313. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Room>(queryText: $"select value(c) from c ",
  314. requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Room-{school_code}") }))
  315. {
  316. school_rooms.Add(item);
  317. }
  318. //List<object> periods = new List<object>();
  319. //List<object> grades = new List<object>();
  320. //var responsesch = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(school_code.ToString(), new PartitionKey($"Base"));
  321. //if (responsesch.Status == 200)
  322. //{
  323. // var jsons = await JsonDocument.ParseAsync(responsesch.ContentStream);
  324. // if (jsons.RootElement.TryGetProperty("period", out JsonElement periodJobj))
  325. // {
  326. // foreach (var periodinfo in periodJobj.EnumerateArray())
  327. // {
  328. // dynamic periodExtobj = new ExpandoObject();
  329. // periodExtobj.id = periodinfo.GetProperty("id");
  330. // periodExtobj.name = periodinfo.GetProperty("name");
  331. // periods.Add(periodExtobj);
  332. // if (periodinfo.TryGetProperty("grades", out JsonElement gradesJobj))
  333. // {
  334. // foreach (var gradeinfo in gradesJobj.EnumerateArray())
  335. // {
  336. // dynamic gradeExtobj = new ExpandoObject();
  337. // gradeExtobj.id = gradeinfo.GetProperty("id");
  338. // gradeExtobj.name = gradeinfo.GetProperty("name");
  339. // gradeExtobj.periodId = periodinfo.GetProperty("id");
  340. // grades.Add(gradeExtobj);
  341. // }
  342. // }
  343. // }
  344. // }
  345. //}
  346. //該老師排定的學校課程
  347. List<object> school_courses = new List<object>();
  348. var query = $"SELECT c.id, c.name, c.teacher, cc.course, c.scope FROM c JOIN cc IN c.courses JOIN cct IN cc.teachers WHERE cct.id = '{id}'";
  349. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"CourseManagement-{school_code}") }))
  350. {
  351. var jsoncm = await JsonDocument.ParseAsync(item.ContentStream);
  352. if (jsoncm.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  353. {
  354. foreach (var obj in jsoncm.RootElement.GetProperty("Documents").EnumerateArray())
  355. {
  356. //班級
  357. string classIdNow = obj.GetProperty("id").ToString();
  358. dynamic classExtobj = new ExpandoObject();
  359. classExtobj.code = $"Class-{school_code}";
  360. classExtobj.id = classIdNow;
  361. classExtobj.name = obj.GetProperty("name").ToString();
  362. classExtobj.teacher = obj.GetProperty("teacher").ToObject<object>();
  363. classExtobj.scope = obj.GetProperty("scope").ToString();
  364. //課程
  365. obj.TryGetProperty("course", out JsonElement courseNow);
  366. string courseIdNow = courseNow.GetProperty("id").ToString();
  367. var courseExist = school_courses.Where((dynamic x) => x.id == courseIdNow).FirstOrDefault();
  368. if (courseExist == null)
  369. {
  370. dynamic courseExtobj = new ExpandoObject();
  371. courseExtobj.id = courseIdNow;
  372. courseExtobj.name = courseNow.GetProperty("name").ToString();
  373. courseExtobj.scope = courseNow.GetProperty("scope").ToString();
  374. courseExtobj.classes = new List<object>();
  375. courseExtobj.classes.Add(classExtobj);
  376. school_courses.Add(courseExtobj);
  377. }
  378. else
  379. {
  380. courseExist.classes.Add(classExtobj);
  381. }
  382. }
  383. }
  384. }
  385. //校本課綱 [式樣未定 先不取]
  386. List<SyllabusRole> school_syllabus = new List<SyllabusRole>();
  387. //await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.period, c.grade, c.semester, c.subject, c.scope, c.resourceCount, c.itemCount, c.children from c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Syllabus-{school_code}") }))
  388. //{
  389. // var jsons = await JsonDocument.ParseAsync(item.ContentStream);
  390. // if (jsons.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  391. // {
  392. // foreach (var obj in jsons.RootElement.GetProperty("Documents").EnumerateArray())
  393. // {
  394. // SyllabusRole syllabusRole = new SyllabusRole();
  395. // syllabusRole.id = obj.GetProperty("id").ToString();
  396. // syllabusRole.name = obj.GetProperty("name").ToString();
  397. // syllabusRole.period = obj.GetProperty("period");
  398. // syllabusRole.grade = obj.GetProperty("grade");
  399. // syllabusRole.semester = obj.GetProperty("semester");
  400. // syllabusRole.subject = obj.GetProperty("subject");
  401. // syllabusRole.resourceCount = obj.GetProperty("resourceCount").GetUInt16();
  402. // syllabusRole.itemCount = obj.GetProperty("itemCount").GetUInt16();
  403. // List<Syllabus> syllabusList = obj.GetProperty("children").ToObject<List<Syllabus>>();
  404. // syllabusList.Insert(0, new Syllabus { id = syllabusRole.id, name = syllabusRole.name, pid = "", order = 0 });
  405. // syllabusList = syllabusList.OrderBy(x => x.order).ToList();
  406. // syllabusRole.structure = CreateSyllabusTree(syllabusList);
  407. // syllabus.Add(syllabusRole);
  408. // }
  409. // //[DEBUG] string jsonString = System.Text.Json.JsonSerializer.Serialize(syllabusRoles);
  410. // }
  411. //}
  412. //取得School Blob 容器位置及SAS
  413. string school_code_blob = school_code.GetString().ToLower();
  414. var container = _azureStorage.GetBlobContainerClient(school_code_blob);
  415. await container.CreateIfNotExistsAsync(PublicAccessType.None); //嘗試創建School容器,如存在則不做任何事,保障容器一定存在
  416. var (blob_uri, blob_sas) = (roles.Contains("admin") || permissions.Contains("schoolAc-upd")) ? _azureStorage.GetBlobContainerSAS(school_code_blob, BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete) : _azureStorage.GetBlobContainerSAS(school_code_blob, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Write);
  417. return Ok(new { auth_token, blob_uri, blob_sas, school_base, school_courses, school_syllabus, school_classes, school_rooms, size });
  418. } catch (Exception ex) {
  419. await _dingDing.SendBotMsg($"IES5,{_option.Location},Teacher/init/get-school-info()\n{ex.Message}{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  420. return BadRequest();
  421. }
  422. }
  423. /// <summary>
  424. /// 取得學校所有列表
  425. /// </summary>
  426. /// <param name="request"></param>
  427. /// <returns></returns>
  428. [ProducesDefaultResponseType]
  429. //[AuthToken(Roles = "teacher")]
  430. [HttpPost("get-school-list")]
  431. public async Task<IActionResult> GetSchoolList(JsonElement request)
  432. {
  433. try
  434. {
  435. //輸入值
  436. string sqlSchoolId = (request.TryGetProperty("schoolId", out JsonElement reqSchoolId)) ? reqSchoolId.GetString() : string.Empty;
  437. bool sqlHasManager = (request.TryGetProperty("manager", out JsonElement reqHasManager)) ? reqHasManager.GetBoolean() : false;
  438. bool sqlHasMBaseModule = (request.TryGetProperty("base", out JsonElement reqHasBaseModule)) ? reqHasBaseModule.GetBoolean() : false;
  439. var client = _azureCosmos.GetCosmosClient();
  440. //有管理者的學校
  441. Dictionary<string, List<Dictionary<string, string>>> managerSchDic = new Dictionary<string, List<Dictionary<string, string>>>();
  442. string managerWhereOption = (!string.IsNullOrWhiteSpace(sqlSchoolId)) ? $" AND c.code = 'Teacher-{sqlSchoolId}'" : string.Empty;
  443. string managerSql = $"SELECT DISTINCT REPLACE(c.code, 'Teacher-', '') AS schoolId, c.id, c.name FROM c WHERE ARRAY_CONTAINS(c.roles, 'admin', true) AND c.pk = 'Teacher' AND c.status = 'join'{managerWhereOption}";
  444. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: managerSql, requestOptions: new QueryRequestOptions() {} ))
  445. {
  446. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  447. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  448. {
  449. string id = obj.GetProperty("id").GetString(); //管理者ID
  450. string name = obj.GetProperty("name").GetString(); //管理者姓名
  451. string schoolId = obj.GetProperty("schoolId").GetString(); //學校ID
  452. Dictionary<string, string> managerDic = new Dictionary<string, string>();
  453. managerDic.Add("id", id);
  454. managerDic.Add("name", name);
  455. if (managerSchDic.ContainsKey(schoolId))
  456. {
  457. managerSchDic[schoolId].Add(managerDic);
  458. }
  459. else
  460. {
  461. List<Dictionary<string, string>> managerList = new List<Dictionary<string, string>>();
  462. managerList.Add(managerDic);
  463. managerSchDic.Add(schoolId, managerList);
  464. }
  465. }
  466. }
  467. //有管理模組的學校
  468. List<string> baseModuleSchList = new List<string>();
  469. string baseModuleWhereOption = (!string.IsNullOrWhiteSpace(sqlSchoolId)) ? $" AND c.id = '{sqlSchoolId}'" : string.Empty;
  470. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id FROM c JOIN serviceProduct IN c.service.product WHERE serviceProduct.prodCode = 'IPDYZYLC'{baseModuleWhereOption}", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Product") }))
  471. {
  472. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  473. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  474. {
  475. string schoolId = obj.GetProperty("id").GetString(); //學校ID
  476. baseModuleSchList.Add(schoolId);
  477. }
  478. }
  479. //學校資料
  480. List<object> schools = new List<object>();
  481. string schoolWhereOption = (!string.IsNullOrWhiteSpace(sqlSchoolId)) ? $" WHERE c.id = '{sqlSchoolId}'" : string.Empty;
  482. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.region, c.province, c.city, c.picture FROM c {schoolWhereOption}", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
  483. {
  484. var jsons = await JsonDocument.ParseAsync(item.ContentStream);
  485. if (jsons.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  486. {
  487. foreach (var obj in jsons.RootElement.GetProperty("Documents").EnumerateArray())
  488. {
  489. dynamic schoolExtobj = new ExpandoObject();
  490. schoolExtobj.id = obj.GetProperty("id").GetString(); //學校ID
  491. schoolExtobj.name = obj.GetProperty("name");
  492. schoolExtobj.region = obj.GetProperty("region");
  493. schoolExtobj.province = obj.GetProperty("province");
  494. schoolExtobj.city = obj.GetProperty("city");
  495. schoolExtobj.picture = obj.GetProperty("picture");
  496. if (managerSchDic.ContainsKey(schoolExtobj.id))
  497. {
  498. schoolExtobj.hasManager = true;
  499. schoolExtobj.managers = managerSchDic[schoolExtobj.id];
  500. }
  501. else
  502. {
  503. schoolExtobj.hasManager = false;
  504. schoolExtobj.managers = new List<object>();
  505. }
  506. schoolExtobj.hasBaseModule = (baseModuleSchList.Contains(schoolExtobj.id)) ? true : false;
  507. //學校輸出結果加入篩選
  508. if( (sqlHasManager && sqlHasMBaseModule) && (schoolExtobj.hasManager && schoolExtobj.hasBaseModule) )
  509. {
  510. schools.Add(schoolExtobj);
  511. }
  512. else if(sqlHasManager && schoolExtobj.hasManager)
  513. {
  514. schools.Add(schoolExtobj);
  515. }
  516. else if(sqlHasMBaseModule && schoolExtobj.hasBaseModule)
  517. {
  518. schools.Add(schoolExtobj);
  519. }
  520. else if(!sqlHasManager && !sqlHasMBaseModule)
  521. {
  522. schools.Add(schoolExtobj);
  523. }
  524. }
  525. }
  526. }
  527. return Ok(new { schools });
  528. }
  529. catch (Exception ex)
  530. {
  531. return BadRequest();
  532. }
  533. }
  534. /// <summary>
  535. /// 申請或同意邀請加入學校
  536. /// </summary>
  537. ///
  538. /// <param name="requert"></param>
  539. /// <returns></returns>
  540. [ProducesDefaultResponseType]
  541. [AuthToken(Roles = "teacher")]
  542. [HttpPost("join-school")]
  543. public async Task<IActionResult> JoinSchool(JsonElement requert)
  544. {
  545. try
  546. {
  547. if (!requert.TryGetProperty("grant_type", out JsonElement grant_type)) return BadRequest(); //"invite":學校邀請 "request":老師申請 "join":"成為學校老師",leave 离开,cancel 取消。
  548. if (!requert.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  549. if (!requert.TryGetProperty("school_name", out JsonElement school_name)) return BadRequest();
  550. ///当邀请某个老师加入学校则需要知道是谁邀请的
  551. //if (!requert.TryGetProperty("school_admin", out JsonElement school_admin)) return BadRequest();
  552. string authtoken = HttpContext.GetXAuth("AuthToken");
  553. if (string.IsNullOrEmpty(authtoken)) return BadRequest();
  554. var jwt = new JwtSecurityToken(authtoken);
  555. var id = jwt.Payload.Sub;
  556. var schoolcode = jwt.Payload.Azp;
  557. var Claims = jwt.Payload.Claims;
  558. jwt.Payload.TryGetValue("name", out object name);
  559. jwt.Payload.TryGetValue("picture", out object picture);
  560. var client = _azureCosmos.GetCosmosClient();
  561. //在老師表找出老師,處理該學校狀態 (老師基本資料應該要存在)
  562. Teacher teacher = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<Teacher>(id, new PartitionKey("Base"));
  563. if (teacher.schools == null)
  564. teacher.schools = new List<Teacher.School>();
  565. var school = teacher.schools?.FirstOrDefault(x => x.schoolId.Equals(school_code.GetString(), StringComparison.OrdinalIgnoreCase));
  566. if (school != null)
  567. school.status = grant_type.GetString();
  568. else
  569. {
  570. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  571. teacher.schools.Add(new Teacher.School() { schoolId = school_code.GetString(), name = school_name.GetString(), status = grant_type.GetString() ,time= now });
  572. }
  573. if (grant_type.GetString() .Equals("leave") || grant_type.GetString() .Equals("cancel"))
  574. {
  575. if (teacher.schools.IsNotEmpty())
  576. {
  577. //获取之前已经加入的学校或者申请的学校
  578. var inSchools = teacher.schools.Where(x => x.schoolId == school_code.GetString()).ToList();
  579. if (inSchools.IsNotEmpty())
  580. {
  581. inSchools.ForEach(x => teacher.schools.Remove(x));
  582. }
  583. }
  584. if (teacher.schools.Count > 0 && teacher.size <= 1)
  585. {
  586. teacher.size = 2;
  587. }
  588. await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<Teacher>(teacher, id, new PartitionKey("Base"));
  589. var sresponse = await client.GetContainer(Constant.TEAMModelOS, "School").DeleteItemStreamAsync(id, new PartitionKey($"Teacher-{school_code}"));
  590. return Ok(new { stauts = 1 });
  591. }
  592. else {
  593. var dft = new List<string>() { "content-read", "exercise-read", "knowledge-read", "syllabus-read" };
  594. if (teacher.schools.Count > 0 && teacher.size <= 1)
  595. {
  596. teacher.size = 2;
  597. }
  598. await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<Teacher>(teacher, id, new PartitionKey("Base"));
  599. //在學校表處理該學校教師帳號的狀態
  600. var sresponse = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(id, new PartitionKey($"Teacher-{school_code}"));
  601. if (sresponse.Status == 200)
  602. {
  603. using var json = await JsonDocument.ParseAsync(sresponse.ContentStream);
  604. SchoolTeacher steacher = json.ToObject<SchoolTeacher>();
  605. steacher.status = grant_type.GetString();
  606. if (grant_type.ToString().Equals("join")) {
  607. if (steacher.permissions.IsNotEmpty())
  608. {
  609. foreach (var d in dft) {
  610. if (!steacher.permissions.Contains(d))
  611. {
  612. steacher.permissions.Add(d);
  613. }
  614. }
  615. }
  616. else {
  617. steacher.permissions = dft;
  618. }
  619. }
  620. var response = await client.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync(steacher, id, new PartitionKey($"Teacher-{school_code}"));
  621. }
  622. else
  623. {
  624. SchoolTeacher st = new SchoolTeacher()
  625. {
  626. pk = "Teacher",
  627. code = $"Teacher-{school_code}",
  628. createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
  629. id = id,
  630. name = name.ToString(),
  631. picture = picture?.ToString(),
  632. permissions = grant_type.ToString().Equals("join")? dft : null,
  633. roles = new List<string>() { "teacher" },
  634. size = 0,
  635. status = grant_type.GetString()
  636. };
  637. var response = await client.GetContainer(Constant.TEAMModelOS, "School").CreateItemAsync(st, new PartitionKey($"Teacher-{school_code}"));
  638. }
  639. Notification notification = null;
  640. List<SchoolTeacher> teachers = new List<SchoolTeacher>();
  641. var queryslt = $"SELECT value(c) FROM c join A1 in c.roles where A1 in ('admin')";
  642. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<SchoolTeacher>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{school_code}") }))
  643. {
  644. teachers.Add(item);
  645. }
  646. if (teachers.IsNotEmpty()) {
  647. string code = grant_type.GetString();
  648. if (grant_type.GetString() .Equals("join")) {
  649. code = "invite-join";
  650. }
  651. notification = new Notification
  652. {
  653. hubName = "hita",
  654. type = "msg",
  655. from = $"ies5:private",
  656. to = teachers.Select(x => x.id).ToList(),
  657. label = $"{code}_school",
  658. body = new {biz= code, tmdid= id ,tmdname= name.ToString(), schoolcode=$"{school_code}", schoolname=$"{school_name}", status =1,time= DateTimeOffset.UtcNow .ToUnixTimeMilliseconds()}.ToJsonString(),
  659. expires= DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeSeconds()
  660. };
  661. }
  662. if (notification != null) {
  663. var url= _configuration.GetValue<string>("HaBookAuth:CoreService:sendnotification");
  664. var clientID= _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  665. var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  666. var location = _option.Location;
  667. var code = await _notificationService .SendNotification(clientID, clientSecret, location, url, notification);
  668. }
  669. return Ok(new { stauts = 1 });
  670. }
  671. }
  672. catch (Exception ex)
  673. {
  674. await _dingDing.SendBotMsg($"TEAMModel,{_option.Location},Init/JoinSchool()\n{ex.Message}{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  675. return BadRequest();
  676. }
  677. }
  678. //課綱的model先記在下面,待式樣確定後再轉換
  679. private List<SyllabusNode> CreateSyllabusTree(List<Syllabus> syllabuses)
  680. {
  681. List<SyllabusNode> nodes = new List<SyllabusNode>();
  682. foreach (var syllabus in syllabuses)
  683. {
  684. if (syllabus.pid .Equals(""))
  685. nodes.Add(new SyllabusNode { id = syllabus.id, name = syllabus.name });
  686. else
  687. {
  688. CreateNode(nodes, syllabus);
  689. }
  690. }
  691. return nodes;
  692. }
  693. private void CreateNode(List<SyllabusNode> nodes, Syllabus parent)
  694. {
  695. foreach (var node in nodes)
  696. {
  697. if (node.id == parent.pid)
  698. {
  699. node.children.Add(new SyllabusNode { id = parent.id, name = parent.name });
  700. }
  701. else
  702. {
  703. CreateNode(node.children, parent);
  704. }
  705. }
  706. }
  707. public class SyllabusRole
  708. {
  709. public string id { get; set; }
  710. public string name { get; set; }
  711. public object period { get; set; }
  712. public object semester { get; set; }
  713. public object grade { get; set; }
  714. public object subject { get; set; }
  715. public int resourceCount { get; set; }
  716. public int itemCount { get; set; }
  717. public object structure { get; set; }
  718. }
  719. public class Syllabus
  720. {
  721. public string id { get; set; }
  722. public string name { get; set; }
  723. public string pid { get; set; }
  724. public int order { get; set; }
  725. }
  726. public class SyllabusNode
  727. {
  728. public string id { get; set; }
  729. public string name { get; set; }
  730. public List<SyllabusNode> children { get; set; }
  731. public SyllabusNode()
  732. {
  733. children = new List<SyllabusNode>();
  734. }
  735. }
  736. }
  737. }