6 Commits

Author SHA1 Message Date
d6a524f5f5 chore(release): v0.0.3 2023-04-26 22:51:02 +03:00
e9b764b72f fix: handle errors 2023-04-26 22:45:40 +03:00
260eadd837 chore: move readyState 2023-04-26 22:44:45 +03:00
ad0c5b2d40 fix: initial pagination 2023-04-26 18:34:51 +03:00
3ac731c5ee feat: initial pagination 2023-04-26 18:31:25 +03:00
f40c48370c feat: duplicate document 2023-04-26 17:45:11 +03:00
9 changed files with 179 additions and 47 deletions

View File

@ -1,6 +1,29 @@
# Changelog
## v0.0.3
[compare changes](https://github.com/arashsheyda/nuxt-mongoose/compare/v0.0.2...v0.0.3)
### 🚀 Enhancements
- Duplicate document ([f40c483](https://github.com/arashsheyda/nuxt-mongoose/commit/f40c483))
- Initial pagination ([3ac731c](https://github.com/arashsheyda/nuxt-mongoose/commit/3ac731c))
### 🩹 Fixes
- Initial pagination ([ad0c5b2](https://github.com/arashsheyda/nuxt-mongoose/commit/ad0c5b2))
- Handle errors ([e9b764b](https://github.com/arashsheyda/nuxt-mongoose/commit/e9b764b))
### 🏡 Chore
- Move readyState ([260eadd](https://github.com/arashsheyda/nuxt-mongoose/commit/260eadd))
### ❤️ Contributors
- Arashsheyda <sheidaeearash1999@gmail.com>
## v0.0.2
[compare changes](https://github.com/arashsheyda/nuxt-mongoose/compare/v0.0.1...v0.0.2)

View File

@ -1,16 +1,13 @@
<script lang="ts" setup>
import './styles/global.css'
const readyState = computedAsync(async () => await rpc.readyState())
</script>
<template>
<Html>
<Body h-screen>
<NuxtLayout v-if="readyState === 1">
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<Connection v-else :connection="readyState" />
</Body>
</Html>
</template>

View File

@ -1,4 +1,3 @@
<!-- eslint-disable no-console -->
<script lang="ts" setup>
const props = defineProps({
collection: {
@ -7,8 +6,19 @@ const props = defineProps({
},
})
// TODO: save in local storage
const pagination = reactive({ limit: 20, page: 1 })
const countDocuments = computedAsync(async () => {
return await rpc.countDocuments(props.collection)
})
const documents = computedAsync(async () => {
return await rpc.listDocuments(props.collection)
return await rpc.listDocuments(props.collection, pagination)
})
watch(pagination, async () => {
documents.value = await rpc.listDocuments(props.collection, pagination)
})
const schema = computedAsync<any>(async () => {
@ -68,19 +78,24 @@ function editDocument(document: any) {
selectedDocument.value = { ...document }
}
async function saveDocument() {
await rpc.createDocument(props.collection, selectedDocument.value)
editing.value = false
selectedDocument.value = undefined
documents.value = await rpc.listDocuments(props.collection)
}
async function saveDocument(document: any, create = true) {
const method = create ? rpc.createDocument : rpc.updateDocument
const newDocument = await method(props.collection, document)
if (newDocument?.error)
return alert(newDocument.error.message)
async function updateDocument() {
// TODO: validate & show errors
await rpc.updateDocument(props.collection, selectedDocument.value)
editing.value = false
selectedDocument.value = undefined
documents.value = await rpc.listDocuments(props.collection)
if (create) {
if (!documents.value.length) {
documents.value = await rpc.listDocuments(props.collection, pagination)
return discardEditing()
}
documents.value.push({ _id: newDocument.insertedId, ...document })
}
else {
const index = documents.value.findIndex((doc: any) => doc._id === newDocument.value._id)
documents.value[index] = document
}
discardEditing()
}
function discardEditing() {
@ -89,9 +104,14 @@ function discardEditing() {
}
async function deleteDocument(document: any) {
rpc.deleteDocument(props.collection, document._id)
documents.value = await rpc.listDocuments(props.collection)
const newDocument = await rpc.deleteDocument(props.collection, document._id)
if (newDocument.deletedCount === 0)
return alert('Failed to delete document')
documents.value = documents.value.filter((doc: any) => doc._id !== document._id)
}
const copy = useCopy()
</script>
<template>
@ -102,9 +122,29 @@ async function deleteDocument(document: any) {
Add Document
</NButton>
</template>
<div op50>
<span v-if="search">{{ filtered.length }} matched · </span>
<span>{{ documents?.length }} documents in total</span>
<div v-if="countDocuments" flex items-center>
<div op50>
<span v-if="search">{{ filtered.length }} matched · </span>
<span>{{ documents?.length }} of {{ countDocuments }} documents in total</span>
</div>
<div flex-auto />
<div flex gap-2>
<NSelect v-if="pagination.limit !== 0" v-model="pagination.page">
<option v-for="i in Math.ceil(countDocuments / pagination.limit)" :key="i" :value="i">
page:
{{ i }}
</option>
</NSelect>
<NSelect v-model="pagination.limit">
<option v-for="i in [1, 2, 3, 4, 5]" :key="i" :value="i * 10">
show:
{{ i * 10 }}
</option>
<option :value="0">
show all
</option>
</NSelect>
</div>
</div>
</Navbar>
<table v-if="documents?.length || selectedDocument" w-full mb10 :class="{ 'editing-mode': editing }">
@ -119,7 +159,6 @@ async function deleteDocument(document: any) {
</tr>
</thead>
<tbody>
<!-- hover-bg-green hover-bg-opacity-5 hover-text-green cursor-pointer -->
<tr v-for="document in filtered" :key="document._id" :class="{ isEditing: editing && selectedDocument._id === document._id }">
<td v-for="field of fields" :key="field" @dblclick="editDocument(document)">
<template v-if="editing && selectedDocument._id === document._id">
@ -130,14 +169,16 @@ async function deleteDocument(document: any) {
</span>
</td>
<td class="actions">
<div flex justify-center gap2>
<div flex justify-center gap2 class="group">
<template v-if="editing && selectedDocument._id === document._id">
<NIconButton icon="carbon-save" @click="updateDocument" />
<NIconButton icon="carbon-save" @click="saveDocument(selectedDocument, false)" />
<NIconButton icon="carbon-close" @click="discardEditing" />
</template>
<template v-else>
<NIconButton icon="carbon-edit" @click="editDocument(document)" />
<NIconButton icon="carbon-delete" @click="deleteDocument(document)" />
<NIconButton icon="carbon-document-multiple-02" @click="saveDocument(document)" />
<NIconButton absolute right-4 opacity-0 group-hover="opacity-100" transition-all icon="carbon-copy" @click="copy(JSON.stringify(document))" />
</template>
</div>
</td>
@ -148,7 +189,7 @@ async function deleteDocument(document: any) {
<input v-else placeholder="ObjectId(_id)" disabled>
</td>
<td flex justify-center gap2 class="actions">
<NIconButton icon="carbon-save" @click="saveDocument" />
<NIconButton icon="carbon-save" @click="saveDocument(selectedDocument)" />
<NIconButton icon="carbon-close" @click="discardEditing" />
</td>
</tr>

View File

@ -0,0 +1,10 @@
import { useClipboard } from '@vueuse/core'
export function useCopy() {
const clipboard = useClipboard()
return (text: string) => {
clipboard.copy(text)
// TODO: show toast
}
}

View File

@ -1,5 +1,10 @@
<script lang="ts" setup>
const readyState = computedAsync(async () => await rpc.readyState())
</script>
<template>
<div h-full of-auto>
<slot />
<slot v-if="readyState === 1" />
<Connection v-else :connection="readyState" />
</div>
</template>

View File

@ -1,7 +1,7 @@
{
"name": "nuxt-mongoose",
"type": "module",
"version": "0.0.2",
"version": "0.0.3",
"description": "Nuxt 3 module for MongoDB with Mongoose",
"license": "MIT",
"repository": {

View File

@ -1,4 +1,3 @@
import { logger } from '@nuxt/kit'
import mongoose from 'mongoose'
import type { NuxtDevtoolsServerContext, ServerFunctions } from '../types'
@ -10,26 +9,69 @@ export function setupDatabaseRPC({ options }: NuxtDevtoolsServerContext): any {
return mongoose.connection.readyState
},
async createCollection(name: string) {
return await mongoose.connection.db.createCollection(name)
try {
return await mongoose.connection.db.createCollection(name)
}
catch (error) {
return ErrorIT(error)
}
},
async listCollections() {
return await mongoose.connection.db.listCollections().toArray()
try {
return await mongoose.connection.db.listCollections().toArray()
}
catch (error) {
return ErrorIT(error)
}
},
async getCollection(name: string) {
return mongoose.connection.db.collection(name)
try {
return await mongoose.connection.db.collection(name).findOne()
}
catch (error) {
return ErrorIT(error)
}
},
async dropCollection(name: string) {
return await mongoose.connection.db.collection(name).drop()
try {
return await mongoose.connection.db.dropCollection(name)
}
catch (error) {
return ErrorIT(error)
}
},
async createDocument(collection: string, data: any) {
return await mongoose.connection.db.collection(collection).insertOne(data)
const { _id, ...rest } = data
try {
return await mongoose.connection.db.collection(collection).insertOne(rest)
}
catch (error: any) {
return ErrorIT(error)
}
},
async listDocuments(collection: string) {
return await mongoose.connection.db.collection(collection).find().toArray()
async countDocuments(collection: string) {
try {
return await mongoose.connection.db.collection(collection).countDocuments()
}
catch (error) {
return ErrorIT(error)
}
},
async getDocument(collection: string, document: {}) {
return await mongoose.connection.db.collection(collection).findOne({ document })
async listDocuments(collection: string, options: { page: number; limit: number } = { page: 1, limit: 10 }) {
const skip = (options.page - 1) * options.limit
const cursor = mongoose.connection.db.collection(collection).find().skip(skip)
if (options.limit !== 0)
cursor.limit(options.limit)
return await cursor.toArray()
},
async getDocument(collection: string, document: any) {
try {
return await mongoose.connection.db.collection(collection).findOne({ document })
}
catch (error) {
return ErrorIT(error)
}
},
async updateDocument(collection: string, data: any) {
const { _id, ...rest } = data
@ -37,12 +79,25 @@ export function setupDatabaseRPC({ options }: NuxtDevtoolsServerContext): any {
return await mongoose.connection.db.collection(collection).findOneAndUpdate({ _id: new mongoose.Types.ObjectId(_id) }, { $set: rest })
}
catch (error) {
logger.log(error)
return error
return ErrorIT(error)
}
},
async deleteDocument(collection: string, id: string) {
return await mongoose.connection.db.collection(collection).deleteOne({ _id: new mongoose.Types.ObjectId(id) })
try {
return await mongoose.connection.db.collection(collection).deleteOne({ _id: new mongoose.Types.ObjectId(id) })
}
catch (error) {
return ErrorIT(error)
}
},
} satisfies Partial<ServerFunctions>
}
function ErrorIT(error: any) {
return {
error: {
message: error?.message,
code: error?.code,
},
}
}

View File

@ -1,11 +1,10 @@
import fs from 'fs-extra'
import { resolve } from 'pathe'
import mongoose from 'mongoose'
import type { Collection, NuxtDevtoolsServerContext, Resource, ServerFunctions } from '../types'
import { generateApiRoute, generateSchemaFile } from '../utils/schematics'
import { capitalize, pluralize, singularize } from '../utils/formatting'
export function setupResourceRPC({ nuxt }: NuxtDevtoolsServerContext): any {
export function setupResourceRPC({ nuxt, rpc }: NuxtDevtoolsServerContext): any {
return {
// TODO: maybe separate functions
async generateResource(collection: Collection, resources: Resource[]) {
@ -45,8 +44,9 @@ export function setupResourceRPC({ nuxt }: NuxtDevtoolsServerContext): any {
}
// create collection if not exists
if (!mongoose.connection.modelNames().includes(dbName))
await mongoose.connection.db.createCollection(plural)
const collections = await rpc.functions.listCollections()
if (!collections.find((c: any) => c.name === plural))
await rpc.functions.createCollection(plural)
},
async resourceSchema(collection: string) {
// TODO: use magicast

View File

@ -8,7 +8,8 @@ export interface ServerFunctions {
// Database - documents
createDocument(collection: string, data: any): Promise<any>
listDocuments(collection: string): Promise<any>
countDocuments(collection: string): Promise<any>
listDocuments(collection: string, options: any): Promise<any>
getDocument(collection: string, id: string): Promise<any>
updateDocument(collection: string, data: any): Promise<any>
deleteDocument(collection: string, id: string): Promise<any>