memi-board 0.22.0 → 0.23.0
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/README.md +26 -40
- package/dist/composables/useMemiBoardComments.d.ts +6 -1
- package/dist/composables/useMemiBoardSettings.d.ts +2 -2
- package/dist/composables/useMemiBoardStorage.d.ts +2 -2
- package/dist/composables/useMemiBoardUserPosts.d.ts +4 -2
- package/dist/composables/useMemiBoardUsers.d.ts +1 -1
- package/dist/composables/useMemiBoardViews.d.ts +1 -1
- package/dist/config.d.ts +8 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +622 -603
- package/dist/index.js.map +1 -1
- package/dist/runtime/components/Attachments.vue +1 -2
- package/dist/runtime/components/CommentItem.vue +8 -7
- package/dist/runtime/components/Detail.vue +8 -7
- package/dist/runtime/components/Editor.vue +1 -2
- package/dist/runtime/components/ListDefault.vue +11 -10
- package/dist/runtime/components/ListDense.vue +8 -7
- package/dist/runtime/components/ListVideo.vue +8 -7
- package/dist/types.d.ts +23 -9
- package/dist/utils/boardPaths.d.ts +26 -15
- package/docs/firebase-setup.md +3 -4
- package/docs/firestore.indexes.json.example +127 -0
- package/docs/firestore.rules.example +107 -67
- package/docs/storage.rules.example +13 -12
- package/package.json +1 -1
|
@@ -5,7 +5,6 @@ import { useMemiBoardStorage } from 'memi-board/runtime'
|
|
|
5
5
|
|
|
6
6
|
const props = withDefaults(defineProps<{
|
|
7
7
|
modelValue: Attachment[]
|
|
8
|
-
boardId: string
|
|
9
8
|
/** 새 글 작성 중에도 미리 생성한 Firestore 자동 ID를 넘긴다. */
|
|
10
9
|
postId: string
|
|
11
10
|
editable?: boolean
|
|
@@ -44,7 +43,7 @@ async function onFileInputChange(e: Event) {
|
|
|
44
43
|
const uploaded: Attachment[] = []
|
|
45
44
|
for (const file of toUpload) {
|
|
46
45
|
uploadProgress.value = 0
|
|
47
|
-
const { promise } = uploadAttachment(file, props.
|
|
46
|
+
const { promise } = uploadAttachment(file, props.postId, (ratio) => {
|
|
48
47
|
uploadProgress.value = ratio
|
|
49
48
|
})
|
|
50
49
|
uploaded.push(await promise)
|
|
@@ -130,18 +130,19 @@ async function toggleBlind() {
|
|
|
130
130
|
<div class="min-w-0 flex-1">
|
|
131
131
|
<div class="flex items-center gap-2">
|
|
132
132
|
<span class="text-sm font-medium">{{ comment.authorName ?? '익명' }}</span>
|
|
133
|
-
<
|
|
134
|
-
:delay-duration="0"
|
|
135
|
-
:text="timestampDetails"
|
|
136
|
-
:ui="{ content: 'h-auto w-max py-2', text: 'whitespace-pre-line overflow-visible' }"
|
|
137
|
-
>
|
|
133
|
+
<UPopover :content="{ side: 'top' }" :ui="{ content: 'h-auto w-max' }">
|
|
138
134
|
<time
|
|
139
135
|
:datetime="date?.toISOString()"
|
|
140
|
-
class="cursor-
|
|
136
|
+
class="cursor-pointer text-xs text-muted"
|
|
141
137
|
>
|
|
142
138
|
{{ relativeDate }}
|
|
143
139
|
</time>
|
|
144
|
-
|
|
140
|
+
<template #content>
|
|
141
|
+
<p class="whitespace-pre-line px-3 py-2 text-xs">
|
|
142
|
+
{{ timestampDetails }}
|
|
143
|
+
</p>
|
|
144
|
+
</template>
|
|
145
|
+
</UPopover>
|
|
145
146
|
</div>
|
|
146
147
|
<form
|
|
147
148
|
v-if="editing"
|
|
@@ -254,18 +254,19 @@ const contentHtml = computed(() => {
|
|
|
254
254
|
<UIcon name="i-lucide-eye" class="size-3.5" />
|
|
255
255
|
{{ post.viewCount ?? 0 }}
|
|
256
256
|
</span>
|
|
257
|
-
<
|
|
258
|
-
:delay-duration="0"
|
|
259
|
-
:text="formatTimestampDetails(post.createdAt, post.updatedAt).join('\n')"
|
|
260
|
-
:ui="{ content: 'h-auto w-max py-2', text: 'whitespace-pre-line overflow-visible' }"
|
|
261
|
-
>
|
|
257
|
+
<UPopover :content="{ side: 'top' }" :ui="{ content: 'h-auto w-max' }">
|
|
262
258
|
<time
|
|
263
259
|
:datetime="post.createdAt?.toDate?.().toISOString()"
|
|
264
|
-
class="cursor-
|
|
260
|
+
class="cursor-pointer"
|
|
265
261
|
>
|
|
266
262
|
{{ formatRelativeDate(post.createdAt, now) }}
|
|
267
263
|
</time>
|
|
268
|
-
|
|
264
|
+
<template #content>
|
|
265
|
+
<p class="whitespace-pre-line px-3 py-2 text-xs">
|
|
266
|
+
{{ formatTimestampDetails(post.createdAt, post.updatedAt).join('\n') }}
|
|
267
|
+
</p>
|
|
268
|
+
</template>
|
|
269
|
+
</UPopover>
|
|
269
270
|
</div>
|
|
270
271
|
<div v-if="canEdit(post) || canDelete(post)" class="flex gap-2">
|
|
271
272
|
<UButton
|
|
@@ -231,7 +231,7 @@ async function doUploadImage(file: File): Promise<EditorImageEntry> {
|
|
|
231
231
|
if (file.size > EDITOR_IMAGE_SOURCE_MAX_BYTES) {
|
|
232
232
|
throw new Error(`원본 이미지는 ${EDITOR_IMAGE_SOURCE_MAX_BYTES / 1024 / 1024}MB 이하여야 합니다.`)
|
|
233
233
|
}
|
|
234
|
-
const entry = await uploadEditorImage(file,
|
|
234
|
+
const entry = await uploadEditorImage(file, imageNamespace())
|
|
235
235
|
uploadedEditorImages.value.push(entry)
|
|
236
236
|
return entry
|
|
237
237
|
}
|
|
@@ -907,7 +907,6 @@ async function handleSubmit() {
|
|
|
907
907
|
<MemiBoardAttachments
|
|
908
908
|
v-if="!isImageListView"
|
|
909
909
|
v-model="attachments"
|
|
910
|
-
:board-id="resolvedBoardId"
|
|
911
910
|
:post-id="attachmentNamespace"
|
|
912
911
|
editable
|
|
913
912
|
/>
|
|
@@ -36,8 +36,8 @@ function image(post: PostModel): string | undefined {
|
|
|
36
36
|
class="flex min-w-0 flex-1 text-left transition-colors hover:bg-elevated/50"
|
|
37
37
|
@click="!postTo(post) && emit('select', post)"
|
|
38
38
|
>
|
|
39
|
-
<!-- 카드
|
|
40
|
-
<div class="
|
|
39
|
+
<!-- 카드 좌·상 밀착, 항상 정사각형 — 텍스트가 길어져도 썸네일 비율은 유지 -->
|
|
40
|
+
<div class="size-[4.5rem] shrink-0 self-start bg-elevated">
|
|
41
41
|
<img
|
|
42
42
|
v-if="image(post)"
|
|
43
43
|
:src="image(post)"
|
|
@@ -46,7 +46,7 @@ function image(post: PostModel): string | undefined {
|
|
|
46
46
|
>
|
|
47
47
|
<div
|
|
48
48
|
v-else
|
|
49
|
-
class="flex h-full
|
|
49
|
+
class="flex h-full w-full items-center justify-center text-muted"
|
|
50
50
|
>
|
|
51
51
|
<UIcon name="i-lucide-image" class="size-6" />
|
|
52
52
|
</div>
|
|
@@ -103,18 +103,19 @@ function image(post: PostModel): string | undefined {
|
|
|
103
103
|
truncate
|
|
104
104
|
class="max-w-full"
|
|
105
105
|
/>
|
|
106
|
-
<
|
|
107
|
-
:delay-duration="0"
|
|
108
|
-
:text="formatTimestampDetails(post.createdAt, post.updatedAt).join('\n')"
|
|
109
|
-
:ui="{ content: 'h-auto w-max py-2', text: 'whitespace-pre-line overflow-visible' }"
|
|
110
|
-
>
|
|
106
|
+
<UPopover :content="{ side: 'top' }" :ui="{ content: 'h-auto w-max' }">
|
|
111
107
|
<time
|
|
112
108
|
:datetime="post.createdAt?.toDate?.().toISOString()"
|
|
113
|
-
class="cursor-
|
|
109
|
+
class="cursor-pointer text-xs leading-snug text-muted"
|
|
114
110
|
>
|
|
115
111
|
{{ formatRelativeDate(post.createdAt, now) }}
|
|
116
112
|
</time>
|
|
117
|
-
|
|
113
|
+
<template #content>
|
|
114
|
+
<p class="whitespace-pre-line px-3 py-2 text-xs">
|
|
115
|
+
{{ formatTimestampDetails(post.createdAt, post.updatedAt).join('\n') }}
|
|
116
|
+
</p>
|
|
117
|
+
</template>
|
|
118
|
+
</UPopover>
|
|
118
119
|
<div class="flex flex-wrap items-center justify-end gap-x-2 gap-y-0.5 text-xs leading-snug text-muted">
|
|
119
120
|
<span class="inline-flex items-center gap-0.5 tabular-nums">
|
|
120
121
|
<UIcon name="i-lucide-eye" class="size-3 shrink-0" />{{ post.viewCount ?? 0 }}
|
|
@@ -93,18 +93,19 @@ function thumb(post: PostModel): string | undefined {
|
|
|
93
93
|
/>
|
|
94
94
|
</span>
|
|
95
95
|
<span class="text-default/40" aria-hidden="true">·</span>
|
|
96
|
-
<
|
|
97
|
-
:delay-duration="0"
|
|
98
|
-
:text="formatTimestampDetails(post.createdAt, post.updatedAt).join('\n')"
|
|
99
|
-
:ui="{ content: 'h-auto w-max py-2', text: 'whitespace-pre-line overflow-visible' }"
|
|
100
|
-
>
|
|
96
|
+
<UPopover :content="{ side: 'top' }" :ui="{ content: 'h-auto w-max' }">
|
|
101
97
|
<time
|
|
102
98
|
:datetime="post.createdAt?.toDate?.().toISOString()"
|
|
103
|
-
class="cursor-
|
|
99
|
+
class="cursor-pointer tabular-nums"
|
|
104
100
|
>
|
|
105
101
|
{{ formatRelativeDate(post.createdAt, now) }}
|
|
106
102
|
</time>
|
|
107
|
-
|
|
103
|
+
<template #content>
|
|
104
|
+
<p class="whitespace-pre-line px-3 py-2 text-xs">
|
|
105
|
+
{{ formatTimestampDetails(post.createdAt, post.updatedAt).join('\n') }}
|
|
106
|
+
</p>
|
|
107
|
+
</template>
|
|
108
|
+
</UPopover>
|
|
108
109
|
<span class="text-default/40" aria-hidden="true">·</span>
|
|
109
110
|
<span class="inline-flex items-center gap-0.5 tabular-nums">
|
|
110
111
|
<UIcon name="i-lucide-eye" class="size-3" />
|
|
@@ -75,18 +75,19 @@ function cover(post: PostModel): string | undefined {
|
|
|
75
75
|
<UIcon name="i-lucide-eye" class="size-3" />
|
|
76
76
|
{{ post.viewCount ?? 0 }}
|
|
77
77
|
</span>
|
|
78
|
-
<
|
|
79
|
-
:delay-duration="0"
|
|
80
|
-
:text="formatTimestampDetails(post.createdAt, post.updatedAt).join('\n')"
|
|
81
|
-
:ui="{ content: 'h-auto w-max py-2', text: 'whitespace-pre-line overflow-visible' }"
|
|
82
|
-
>
|
|
78
|
+
<UPopover :content="{ side: 'top' }" :ui="{ content: 'h-auto w-max' }">
|
|
83
79
|
<time
|
|
84
80
|
:datetime="post.createdAt?.toDate?.().toISOString()"
|
|
85
|
-
class="cursor-
|
|
81
|
+
class="cursor-pointer"
|
|
86
82
|
>
|
|
87
83
|
{{ formatRelativeDate(post.createdAt, now) }}
|
|
88
84
|
</time>
|
|
89
|
-
|
|
85
|
+
<template #content>
|
|
86
|
+
<p class="whitespace-pre-line px-3 py-2 text-xs">
|
|
87
|
+
{{ formatTimestampDetails(post.createdAt, post.updatedAt).join('\n') }}
|
|
88
|
+
</p>
|
|
89
|
+
</template>
|
|
90
|
+
</UPopover>
|
|
90
91
|
</div>
|
|
91
92
|
</div>
|
|
92
93
|
</div>
|
package/dist/types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface Attachment {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* 에디터 본문 이미지 업로드 결과 (원본 + 썸네일).
|
|
11
|
-
* Storage: `
|
|
11
|
+
* Storage: `memiBoardPosts/{postId}/images/...` · `.../images/thumbnails/...`
|
|
12
12
|
* 본문 markdown 에는 originalUrl 만 넣고, 썸네일은 목록 카드 등에 활용 가능.
|
|
13
13
|
* 수정 중 버려진 파일은 호스트 스케줄러로 정리 (본문에 없는 path).
|
|
14
14
|
*/
|
|
@@ -22,6 +22,8 @@ export interface EditorImageEntry {
|
|
|
22
22
|
export type ModerationStatus = 'approved';
|
|
23
23
|
export interface PostModel {
|
|
24
24
|
id?: string;
|
|
25
|
+
/** 어느 보드(memiBoardSettings/{boardId})에 속한 글인지 — flat 컬렉션이라 경로 대신 필드로 스코핑한다. */
|
|
26
|
+
boardId: string;
|
|
25
27
|
/** 카테고리 내 URL 식별자. 최초 작성 시 생성되고 제목 수정 시에도 유지된다. */
|
|
26
28
|
slug: string;
|
|
27
29
|
title: string;
|
|
@@ -32,14 +34,14 @@ export interface PostModel {
|
|
|
32
34
|
videoUrl?: string;
|
|
33
35
|
tags?: string[];
|
|
34
36
|
/**
|
|
35
|
-
* @deprecated 카테고리 개념 제거 —
|
|
37
|
+
* @deprecated 카테고리 개념 제거 — boardId 로 대체.
|
|
36
38
|
* 레거시 읽기 호환용으로만 남을 수 있음.
|
|
37
39
|
*/
|
|
38
40
|
category?: string;
|
|
39
41
|
attachments?: Attachment[];
|
|
40
42
|
/** 클라이언트 batch로 증감하는 UI 편의 필드 — 보안 판단에 쓰지 않는다. */
|
|
41
43
|
commentCount: number;
|
|
42
|
-
/** 클라이언트 트랜잭션으로 증감하는 UI 편의 필드 — 보안 판단에 쓰지 않는다. 실제 좋아요 여부는
|
|
44
|
+
/** 클라이언트 트랜잭션으로 증감하는 UI 편의 필드 — 보안 판단에 쓰지 않는다. 실제 좋아요 여부는 memiBoardLikes 컬렉션이 기준. */
|
|
43
45
|
likeCount: number;
|
|
44
46
|
/**
|
|
45
47
|
* 조회수. 로그인 없이 +1 가능(rules 가 viewCount 단독 증가만 허용).
|
|
@@ -69,13 +71,17 @@ export interface PostModel {
|
|
|
69
71
|
export interface PostDetail extends PostModel {
|
|
70
72
|
content: string;
|
|
71
73
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
/**
|
|
75
|
+
* @deprecated PostModel 에 boardId 가 이미 있어 더 이상 별도 타입이 필요 없다.
|
|
76
|
+
* 보드 경계를 넘는 작성자별 글 목록(예: /board-user/{uid}) 등에서 PostModel 을 그대로 쓰면 된다.
|
|
77
|
+
*/
|
|
78
|
+
export type UserPostModel = PostModel;
|
|
76
79
|
export interface CommentModel {
|
|
77
80
|
id?: string;
|
|
81
|
+
/** 속한 글. flat 컬렉션이라 경로 대신 필드로 스코핑한다. */
|
|
78
82
|
postId: string;
|
|
83
|
+
/** 부모 글의 boardId — 생성 시 클라이언트가 채우고, rules 가 부모 글과 일치하는지 검증한다. */
|
|
84
|
+
boardId: string;
|
|
79
85
|
body: string;
|
|
80
86
|
authorUid: string;
|
|
81
87
|
authorName: string | null;
|
|
@@ -116,7 +122,7 @@ export type BoardWriteRole = 'user' | 'staff' | 'admin';
|
|
|
116
122
|
export type BoardVisibility = 'public' | 'hidden';
|
|
117
123
|
/**
|
|
118
124
|
* 보드 1개 메타 (= 예전 BoardCategory).
|
|
119
|
-
* id 가 곧 boardId —
|
|
125
|
+
* id 가 곧 boardId — memiBoardSettings/{id} 에 저장.
|
|
120
126
|
*/
|
|
121
127
|
export interface BoardModel {
|
|
122
128
|
/** boardId (예: 'notice', 'free') */
|
|
@@ -141,10 +147,18 @@ export interface BoardModel {
|
|
|
141
147
|
}
|
|
142
148
|
/** @deprecated BoardModel 사용 — 카테고리 개념 제거, id = boardId */
|
|
143
149
|
export type BoardCategory = BoardModel;
|
|
144
|
-
/**
|
|
150
|
+
/** memiBoardSettings/{boardId} */
|
|
145
151
|
export interface BoardSettingsModel extends Omit<BoardModel, 'id'> {
|
|
146
152
|
updatedAt?: Timestamp;
|
|
147
153
|
}
|
|
154
|
+
/** memiBoardLikes/{postId}_{uid} — "내가 좋아요한 글" 등 uid 기준 교차 조회용 flat 컬렉션. */
|
|
155
|
+
export interface BoardLikeModel {
|
|
156
|
+
id?: string;
|
|
157
|
+
uid: string;
|
|
158
|
+
postId: string;
|
|
159
|
+
boardId: string;
|
|
160
|
+
createdAt: Timestamp;
|
|
161
|
+
}
|
|
148
162
|
/** 검열이 어느 단계에서 끝났는지 (호스트/로깅용, UI 노출 없음) */
|
|
149
163
|
export type ModerationVia = 'empty' | 'disabled' | 'local' | 'ai' | 'restricted' | 'ai-error-allow' | 'ai-error-block';
|
|
150
164
|
export interface ModerationResult {
|
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
import { CollectionReference, DocumentReference, Firestore } from 'firebase/firestore';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const DEFAULT_POSTS_COLLECTION = "memiBoardPosts";
|
|
3
|
+
export declare const DEFAULT_COMMENTS_COLLECTION = "memiBoardComments";
|
|
4
|
+
export declare const DEFAULT_LIKES_COLLECTION = "memiBoardLikes";
|
|
5
|
+
export declare const DEFAULT_SETTINGS_COLLECTION = "memiBoardSettings";
|
|
3
6
|
export declare const DEFAULT_USERS_COLLECTION = "memiBoardUsers";
|
|
4
7
|
export interface BoardPathConfig {
|
|
5
|
-
|
|
8
|
+
postsCollection: string;
|
|
9
|
+
commentsCollection: string;
|
|
10
|
+
likesCollection: string;
|
|
11
|
+
settingsCollection: string;
|
|
6
12
|
usersCollection: string;
|
|
7
13
|
}
|
|
8
14
|
export declare function resolveBoardPathConfig(input: {
|
|
9
|
-
|
|
15
|
+
postsCollection?: string;
|
|
16
|
+
commentsCollection?: string;
|
|
17
|
+
likesCollection?: string;
|
|
18
|
+
settingsCollection?: string;
|
|
10
19
|
usersCollection?: string;
|
|
11
20
|
}): BoardPathConfig;
|
|
12
|
-
|
|
13
|
-
export declare function
|
|
14
|
-
|
|
15
|
-
export declare function
|
|
16
|
-
export declare function
|
|
17
|
-
export declare function
|
|
18
|
-
export declare function
|
|
19
|
-
export declare function
|
|
20
|
-
export declare function
|
|
21
|
-
|
|
21
|
+
/** 보드 설정 목록/문서 — 구 memiBoards/{boardId} stub + settings/config 통합 */
|
|
22
|
+
export declare function settingsCol(db: Firestore, cfg: BoardPathConfig): CollectionReference;
|
|
23
|
+
export declare function settingsDoc(db: Firestore, cfg: BoardPathConfig, boardId: string): DocumentReference;
|
|
24
|
+
export declare function postsCol(db: Firestore, cfg: BoardPathConfig): CollectionReference;
|
|
25
|
+
export declare function postDoc(db: Firestore, cfg: BoardPathConfig, postId: string): DocumentReference;
|
|
26
|
+
export declare function postBodyDoc(db: Firestore, cfg: BoardPathConfig, postId: string): DocumentReference;
|
|
27
|
+
export declare function commentsCol(db: Firestore, cfg: BoardPathConfig): CollectionReference;
|
|
28
|
+
export declare function commentDoc(db: Firestore, cfg: BoardPathConfig, commentId: string): DocumentReference;
|
|
29
|
+
export declare function likesCol(db: Firestore, cfg: BoardPathConfig): CollectionReference;
|
|
30
|
+
/** 좋아요 문서ID 관례: `${postId}_${uid}` — 유저당 글당 좋아요 1개, 토글은 create-or-delete */
|
|
31
|
+
export declare function likeDocId(postId: string, uid: string): string;
|
|
32
|
+
export declare function likeDoc(db: Firestore, cfg: BoardPathConfig, postId: string, uid: string): DocumentReference;
|
|
22
33
|
export declare function boardUsersCol(db: Firestore, cfg: BoardPathConfig): CollectionReference;
|
|
23
34
|
export declare function boardUserDoc(db: Firestore, cfg: BoardPathConfig, uid: string): DocumentReference;
|
|
24
|
-
|
|
25
|
-
export declare function
|
|
35
|
+
/** postId 가 전역 고유이므로 boardId 프리픽스 불필요 */
|
|
36
|
+
export declare function postStorageFolder(cfg: BoardPathConfig, postId: string): string;
|
|
26
37
|
export declare function boardSsrKey(cfg: BoardPathConfig, suffix: string): string;
|
package/docs/firebase-setup.md
CHANGED
|
@@ -94,13 +94,12 @@ AI Logic / `firebaseml` 이 **Enforce** 이면 App Check 토큰 없이 검열 AP
|
|
|
94
94
|
4. **프로덕션:** 디버그 토큰 사용 금지. reCAPTCHA 도메인에 실제 호스트 도메인 등록.
|
|
95
95
|
5. (선택) Firestore/Storage Enforce — 켜면 토큰 없는 요청 거부. AI Logic 과 별개 토글이다.
|
|
96
96
|
|
|
97
|
-
## 9. Firestore 복합 인덱스
|
|
97
|
+
## 9. Firestore 복합 인덱스
|
|
98
98
|
|
|
99
|
-
`
|
|
100
|
-
콘솔 에러의 인덱스 생성 링크를 따르거나, 호스트 `firestore.indexes.json` 에 추가 후:
|
|
99
|
+
`memiBoardPosts`/`memiBoardComments`/`memiBoardLikes` 는 모두 최상위 flat 컬렉션이고, 보드/글 단위 스코핑은 `boardId`/`postId` 필드에 대한 `where` 동등 필터로 한다. [docs/firestore.indexes.json.example](firestore.indexes.json.example) 를 호스트 `firestore.indexes.json` 에 병합 후:
|
|
101
100
|
|
|
102
101
|
```bash
|
|
103
102
|
firebase deploy --only firestore:indexes
|
|
104
103
|
```
|
|
105
104
|
|
|
106
|
-
|
|
105
|
+
콘솔 에러에 인덱스 생성 링크가 뜨면 그걸 따라도 된다 — 위 예시 파일은 패키지가 실제로 실행하는 쿼리 기준으로 미리 정리해 둔 것이다. 컬렉션 이름을 커스터마이즈했다면(`memiBoard.postsCollection` 등) `collectionGroup` 값도 맞춰 바꾼다.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"indexes": [
|
|
3
|
+
{
|
|
4
|
+
"collectionGroup": "memiBoardPosts",
|
|
5
|
+
"queryScope": "COLLECTION",
|
|
6
|
+
"fields": [
|
|
7
|
+
{ "fieldPath": "boardId", "order": "ASCENDING" },
|
|
8
|
+
{ "fieldPath": "isPublished", "order": "ASCENDING" },
|
|
9
|
+
{ "fieldPath": "listed", "order": "ASCENDING" },
|
|
10
|
+
{ "fieldPath": "createdAt", "order": "DESCENDING" },
|
|
11
|
+
{ "fieldPath": "__name__", "order": "DESCENDING" }
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"collectionGroup": "memiBoardPosts",
|
|
16
|
+
"queryScope": "COLLECTION",
|
|
17
|
+
"fields": [
|
|
18
|
+
{ "fieldPath": "boardId", "order": "ASCENDING" },
|
|
19
|
+
{ "fieldPath": "isPublished", "order": "ASCENDING" },
|
|
20
|
+
{ "fieldPath": "createdAt", "order": "DESCENDING" },
|
|
21
|
+
{ "fieldPath": "__name__", "order": "DESCENDING" }
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"collectionGroup": "memiBoardPosts",
|
|
26
|
+
"queryScope": "COLLECTION",
|
|
27
|
+
"fields": [
|
|
28
|
+
{ "fieldPath": "boardId", "order": "ASCENDING" },
|
|
29
|
+
{ "fieldPath": "isPublished", "order": "ASCENDING" },
|
|
30
|
+
{ "fieldPath": "listed", "order": "ASCENDING" },
|
|
31
|
+
{ "fieldPath": "createdAt", "order": "ASCENDING" },
|
|
32
|
+
{ "fieldPath": "__name__", "order": "ASCENDING" }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"collectionGroup": "memiBoardPosts",
|
|
37
|
+
"queryScope": "COLLECTION",
|
|
38
|
+
"fields": [
|
|
39
|
+
{ "fieldPath": "boardId", "order": "ASCENDING" },
|
|
40
|
+
{ "fieldPath": "isPublished", "order": "ASCENDING" },
|
|
41
|
+
{ "fieldPath": "createdAt", "order": "ASCENDING" },
|
|
42
|
+
{ "fieldPath": "__name__", "order": "ASCENDING" }
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"collectionGroup": "memiBoardPosts",
|
|
47
|
+
"queryScope": "COLLECTION",
|
|
48
|
+
"fields": [
|
|
49
|
+
{ "fieldPath": "boardId", "order": "ASCENDING" },
|
|
50
|
+
{ "fieldPath": "authorUid", "order": "ASCENDING" },
|
|
51
|
+
{ "fieldPath": "isPublished", "order": "ASCENDING" },
|
|
52
|
+
{ "fieldPath": "createdAt", "order": "DESCENDING" },
|
|
53
|
+
{ "fieldPath": "__name__", "order": "DESCENDING" }
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"collectionGroup": "memiBoardPosts",
|
|
58
|
+
"queryScope": "COLLECTION",
|
|
59
|
+
"fields": [
|
|
60
|
+
{ "fieldPath": "authorUid", "order": "ASCENDING" },
|
|
61
|
+
{ "fieldPath": "isPublished", "order": "ASCENDING" },
|
|
62
|
+
{ "fieldPath": "listed", "order": "ASCENDING" },
|
|
63
|
+
{ "fieldPath": "createdAt", "order": "DESCENDING" },
|
|
64
|
+
{ "fieldPath": "__name__", "order": "DESCENDING" }
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"collectionGroup": "memiBoardComments",
|
|
69
|
+
"queryScope": "COLLECTION",
|
|
70
|
+
"fields": [
|
|
71
|
+
{ "fieldPath": "postId", "order": "ASCENDING" },
|
|
72
|
+
{ "fieldPath": "createdAt", "order": "DESCENDING" },
|
|
73
|
+
{ "fieldPath": "__name__", "order": "DESCENDING" }
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"collectionGroup": "memiBoardComments",
|
|
78
|
+
"queryScope": "COLLECTION",
|
|
79
|
+
"fields": [
|
|
80
|
+
{ "fieldPath": "postId", "order": "ASCENDING" },
|
|
81
|
+
{ "fieldPath": "parentId", "order": "ASCENDING" },
|
|
82
|
+
{ "fieldPath": "createdAt", "order": "DESCENDING" },
|
|
83
|
+
{ "fieldPath": "__name__", "order": "DESCENDING" }
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"collectionGroup": "memiBoardComments",
|
|
88
|
+
"queryScope": "COLLECTION",
|
|
89
|
+
"fields": [
|
|
90
|
+
{ "fieldPath": "postId", "order": "ASCENDING" },
|
|
91
|
+
{ "fieldPath": "parentId", "order": "ASCENDING" },
|
|
92
|
+
{ "fieldPath": "createdAt", "order": "ASCENDING" },
|
|
93
|
+
{ "fieldPath": "__name__", "order": "ASCENDING" }
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"collectionGroup": "memiBoardComments",
|
|
98
|
+
"queryScope": "COLLECTION",
|
|
99
|
+
"fields": [
|
|
100
|
+
{ "fieldPath": "rootId", "order": "ASCENDING" },
|
|
101
|
+
{ "fieldPath": "isReply", "order": "ASCENDING" },
|
|
102
|
+
{ "fieldPath": "createdAt", "order": "DESCENDING" },
|
|
103
|
+
{ "fieldPath": "__name__", "order": "DESCENDING" }
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"collectionGroup": "memiBoardComments",
|
|
108
|
+
"queryScope": "COLLECTION",
|
|
109
|
+
"fields": [
|
|
110
|
+
{ "fieldPath": "rootId", "order": "ASCENDING" },
|
|
111
|
+
{ "fieldPath": "isReply", "order": "ASCENDING" },
|
|
112
|
+
{ "fieldPath": "createdAt", "order": "ASCENDING" },
|
|
113
|
+
{ "fieldPath": "__name__", "order": "ASCENDING" }
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"collectionGroup": "memiBoardLikes",
|
|
118
|
+
"queryScope": "COLLECTION",
|
|
119
|
+
"fields": [
|
|
120
|
+
{ "fieldPath": "uid", "order": "ASCENDING" },
|
|
121
|
+
{ "fieldPath": "createdAt", "order": "DESCENDING" },
|
|
122
|
+
{ "fieldPath": "__name__", "order": "DESCENDING" }
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"fieldOverrides": []
|
|
127
|
+
}
|