173 throw new InvalidOperationException(
"App_Data 경로가 비어 있습니다.");
174 if (
string.IsNullOrWhiteSpace(slug))
175 throw new InvalidOperationException(
"가족 슬러그가 비어 있습니다.");
176 if (
string.IsNullOrWhiteSpace(post.
Id))
177 post.Id = Guid.NewGuid().ToString(
"N");
179 var tenantDir = Path.Combine(
AppDataRoot,
"Family", slug);
180 var postsDir = Path.Combine(tenantDir,
"posts");
181 var mediaDir = Path.Combine(tenantDir,
"media", post.
Id);
183 Directory.CreateDirectory(postsDir);
184 Directory.CreateDirectory(mediaDir);
187 foreach (var src
in localPhotoPaths)
189 if (!File.Exists(src))
continue;
190 var fn = Path.GetFileName(src);
191 var dst = Path.Combine(mediaDir, fn);
192 File.Copy(src, dst, overwrite:
true);
193 if (!post.PhotoFileNames.Contains(fn))
194 post.PhotoFileNames.Add(fn);
197 var path = Path.Combine(postsDir, $
"{Sanitize(post.Id)}.json");
198 var tmp = Path.Combine(postsDir, $
"{Sanitize(post.Id)}.{Guid.NewGuid():N}.tmp");
201 await
using (var fs = File.Create(tmp))
202 await JsonSerializer.SerializeAsync(fs, post,
_opts);
203 File.Move(tmp, path, overwrite:
true);
207 throw new IOException($
"JSON 저장 실패: {path} ({ex.Message})", ex);
213 if (File.Exists(tmp)) File.Delete(tmp);
292 var dir = Path.Combine(
AppDataRoot,
"Family", slug,
"posts");
293 if (!Directory.Exists(dir))
return (
null, 0);
295 DateTime? lastPostedAt =
null;
298 foreach (var f
in Directory.GetFiles(dir,
"*.json"))
302 var post = JsonSerializer.Deserialize<
PostEntry>(File.ReadAllText(f),
_opts);
305 if (lastPostedAt is
null || post.PostedAt > lastPostedAt.Value)
306 lastPostedAt = post.PostedAt;
313 return (lastPostedAt, lastNumber);