schemas #22
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": ["Nostr"]
|
"cSpell.words": ["biodynamic", "Nostr", "npub", "RRP", "screwcap"]
|
||||||
}
|
}
|
||||||
|
33
package-lock.json
generated
33
package-lock.json
generated
@ -9,8 +9,10 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"currency-codes-ts": "^3.0.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
|
"i18n-iso-countries": "^7.14.0",
|
||||||
"mongodb": "^6.15.0"
|
"mongodb": "^6.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -2971,6 +2973,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/currency-codes-ts": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/currency-codes-ts/-/currency-codes-ts-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZJeCpq5uY2t8dDl4xdF15shkp5o8jrHcD4lHftK/O8j8xTHlXg0E5YhpZbRJvnLRaKe+JQh1/q1AI9Wc2Dl3Nw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash-es": "^4.17.21"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
@ -3071,6 +3085,12 @@
|
|||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/diacritics": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/diff": {
|
"node_modules/diff": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||||
@ -4553,6 +4573,18 @@
|
|||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18n-iso-countries": {
|
||||||
|
"version": "7.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.14.0.tgz",
|
||||||
|
"integrity": "sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"diacritics": "1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
@ -5482,7 +5514,6 @@
|
|||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.capitalize": {
|
"node_modules/lodash.capitalize": {
|
||||||
|
@ -26,8 +26,10 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "Cellar Social API",
|
"description": "Cellar Social API",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"currency-codes-ts": "^3.0.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
|
"i18n-iso-countries": "^7.14.0",
|
||||||
"mongodb": "^6.15.0"
|
"mongodb": "^6.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import express, { Express } from 'express'
|
import express, { Express } from 'express'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
import { connectToDatabase } from './services/database.service'
|
import { connectToDatabase } from './services/database.service'
|
||||||
import { usersRouter, nostrRouter, reviewRouter } from './routes'
|
import { usersRouter, nostrRouter, reviewsRouter, winesRouter } from './routes'
|
||||||
import { Routes } from './types'
|
import { Routes } from './types'
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
@ -13,7 +13,8 @@ connectToDatabase()
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
app.use(Routes.Users, usersRouter)
|
app.use(Routes.Users, usersRouter)
|
||||||
app.use(Routes.NostrEvents, nostrRouter)
|
app.use(Routes.NostrEvents, nostrRouter)
|
||||||
app.use(Routes.Reviews, reviewRouter)
|
app.use(Routes.Reviews, reviewsRouter)
|
||||||
|
app.use(Routes.Wines, winesRouter)
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server started at http://localhost:${port}`)
|
console.log(`Server started at http://localhost:${port}`)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export * from './user'
|
export * from './user'
|
||||||
export * from './nostrEvent'
|
export * from './nostrEvent'
|
||||||
export * from './review'
|
export * from './review'
|
||||||
|
export * from './wine'
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
export class NostrEvent {
|
export class NostrEvent {
|
||||||
constructor(
|
constructor(
|
||||||
public id: string,
|
public nostrId: string, // nostr unique identifier
|
||||||
public pubkey: string,
|
public pubkey: string, // public key of the event creator
|
||||||
public created_at: number,
|
public created_at: number, // timestamp
|
||||||
public kind: number,
|
public kind: number, // event type, e.g., review, article, comment
|
||||||
public tags: [string][],
|
public tags: [string][], // array of keywords or hashtags
|
||||||
public content: string
|
public content: string // text content of the event
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ import { ObjectId } from 'mongodb'
|
|||||||
|
|
||||||
export class Review {
|
export class Review {
|
||||||
constructor(
|
constructor(
|
||||||
public eventId: string,
|
public eventId: string, // foreign key referencing the nostrEvents collection
|
||||||
public productId: string,
|
public productId: string, // unique identifier for the product
|
||||||
public rating: number,
|
public rating: number, // numerical rating, e.g., 1-100
|
||||||
public reviewText: string,
|
public reviewText: string, // text content of the review
|
||||||
public testingNotes: string[],
|
public tastingNotes: string[], // array of tasting notes, e.g., flavours, aromas
|
||||||
public id?: ObjectId
|
public id?: ObjectId // database object id
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { ObjectId } from 'mongodb'
|
import { ObjectId } from 'mongodb'
|
||||||
|
import { UserRole } from '../types'
|
||||||
|
|
||||||
export class User {
|
export class User {
|
||||||
constructor(
|
constructor(
|
||||||
public name: string,
|
public name: string,
|
||||||
|
public npub: string[],
|
||||||
|
public role: UserRole,
|
||||||
public id?: ObjectId
|
public id?: ObjectId
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
35
src/models/wine.ts
Normal file
35
src/models/wine.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { ObjectId } from 'mongodb'
|
||||||
|
import { WineType, Viticulture, BottleClosure } from '../types'
|
||||||
|
import { Alpha2Code } from 'i18n-iso-countries'
|
||||||
|
import { CurrencyCode } from 'currency-codes-ts/dist/types'
|
||||||
|
|
||||||
|
export class Wine {
|
||||||
|
constructor(
|
||||||
|
public productCodeEAN: string, // Article Number (https://en.wikipedia.org/wiki/International_Article_Number)
|
||||||
|
public productCodeUPC: string, // Product Code (https://en.wikipedia.org/wiki/Universal_Product_Code)
|
||||||
|
public productCodeSKU: string, // Stock keeping unit (https://en.wikipedia.org/wiki/Stock_keeping_unit)
|
||||||
|
public type: WineType, // numerical rating, e.g., 1-100
|
||||||
|
public style: string, // bubbles+fizz, table, dessert, fortified, vermouth
|
||||||
|
public characteristic: string, // light aromatic, textural, fruit forward, structural & savoury, powerful
|
||||||
|
public country: Alpha2Code, // two-letter country codes defined in ISO 3166-1 (https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
||||||
|
public region: string, // appellation, village, sub-region, vineyard
|
||||||
|
public name: string, // label
|
||||||
|
public producerId: ObjectId, // product producer
|
||||||
|
public varietal: string, // if more than one, list as 'blend'
|
||||||
|
public vintage: string, // year, nv (non-vintage) or mv (multi-vintage)
|
||||||
|
public alcohol: number, // alcohol percentage
|
||||||
|
public standardDrinks: number, // number representing an amount of standard drinks per bottle
|
||||||
|
public viticulture: Viticulture, // two-letter country codes
|
||||||
|
public sulfites: number, // parts per million
|
||||||
|
public filtered: boolean, // is wine filtered (fined (egg or fish))
|
||||||
|
public vegan: boolean,
|
||||||
|
public kosher: boolean,
|
||||||
|
public closure: BottleClosure, // cork, crown-seal, screwcap
|
||||||
|
public RRPamount: number, // 20
|
||||||
|
public RRPcurrency: CurrencyCode, // USD
|
||||||
|
public description: string, // detailed description of the product
|
||||||
|
public url?: string, // e.g. producer's website
|
||||||
|
public image?: string, // (optional image URL)cellar.social
|
||||||
|
public id?: ObjectId // database object id
|
||||||
|
) {}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
export * from './users.router'
|
export * from './users.router'
|
||||||
export * from './nostr.router'
|
export * from './nostr.router'
|
||||||
export * from './reviews.router'
|
export * from './reviews.router'
|
||||||
|
export * from './wines.router'
|
||||||
|
@ -3,12 +3,12 @@ import { collections } from '../services/database.service'
|
|||||||
import { Review } from '../models'
|
import { Review } from '../models'
|
||||||
|
|
||||||
// Global Config
|
// Global Config
|
||||||
export const reviewRouter = express.Router()
|
export const reviewsRouter = express.Router()
|
||||||
|
|
||||||
reviewRouter.use(express.json())
|
reviewsRouter.use(express.json())
|
||||||
|
|
||||||
// GET
|
// GET
|
||||||
reviewRouter.get('/', async (_req: Request, res: Response) => {
|
reviewsRouter.get('/', async (_req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const reviews = await collections.reviews?.find({}).toArray()
|
const reviews = await collections.reviews?.find({}).toArray()
|
||||||
|
|
||||||
@ -23,9 +23,10 @@ reviewRouter.get('/', async (_req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// POST
|
// POST
|
||||||
reviewRouter.post('/', async (req: Request, res: Response) => {
|
reviewsRouter.post('/', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const review = req.body as Review
|
const review = req.body as Review
|
||||||
|
|
||||||
const result = await collections.reviews?.insertOne(review)
|
const result = await collections.reviews?.insertOne(review)
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
46
src/routes/wines.router.ts
Normal file
46
src/routes/wines.router.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import express, { Request, Response } from 'express'
|
||||||
|
import { collections } from '../services/database.service'
|
||||||
|
import { Wine } from '../models'
|
||||||
|
|
||||||
|
// Global Config
|
||||||
|
export const winesRouter = express.Router()
|
||||||
|
|
||||||
|
winesRouter.use(express.json())
|
||||||
|
|
||||||
|
// GET
|
||||||
|
winesRouter.get('/', async (_req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const wines = await collections.wines?.find({}).toArray()
|
||||||
|
|
||||||
|
res.status(200).send(wines)
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
res.status(500).send(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// POST
|
||||||
|
winesRouter.post('/', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const wine = req.body as Wine
|
||||||
|
|
||||||
|
const result = await collections.wines?.insertOne(wine)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
res
|
||||||
|
.status(201)
|
||||||
|
.send(`Successfully created a new wine with id ${result.insertedId}`)
|
||||||
|
} else {
|
||||||
|
res.status(500).send('Failed to create a new wine.')
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
res.status(400).send(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
@ -7,6 +7,7 @@ export const collections: {
|
|||||||
[DBcollections.Users]?: mongoDB.Collection
|
[DBcollections.Users]?: mongoDB.Collection
|
||||||
[DBcollections.NostrEvents]?: mongoDB.Collection
|
[DBcollections.NostrEvents]?: mongoDB.Collection
|
||||||
[DBcollections.Reviews]?: mongoDB.Collection
|
[DBcollections.Reviews]?: mongoDB.Collection
|
||||||
|
[DBcollections.Wines]?: mongoDB.Collection
|
||||||
} = {}
|
} = {}
|
||||||
|
|
||||||
// Initialize Connection
|
// Initialize Connection
|
||||||
@ -32,10 +33,12 @@ export async function connectToDatabase() {
|
|||||||
const reviewsCollection: mongoDB.Collection = db.collection(
|
const reviewsCollection: mongoDB.Collection = db.collection(
|
||||||
DBcollections.Reviews
|
DBcollections.Reviews
|
||||||
)
|
)
|
||||||
|
const winesCollection: mongoDB.Collection = db.collection(DBcollections.Wines)
|
||||||
|
|
||||||
collections.users = usersCollection
|
collections.users = usersCollection
|
||||||
collections.nostrEvents = nostrEventsCollection
|
collections.nostrEvents = nostrEventsCollection
|
||||||
collections.reviews = reviewsCollection
|
collections.reviews = reviewsCollection
|
||||||
|
collections.wines = winesCollection
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Successfully connected to database: ${db.databaseName} and collections:
|
`Successfully connected to database: ${db.databaseName} and collections:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export enum DBcollections {
|
export enum DBcollections {
|
||||||
Users = 'users',
|
Users = 'users',
|
||||||
NostrEvents = 'nostrEvents',
|
NostrEvents = 'nostrEvents',
|
||||||
Reviews = 'reviews'
|
Reviews = 'reviews',
|
||||||
|
Wines = 'wines'
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
export * from './database'
|
export * from './database'
|
||||||
export * from './routes'
|
export * from './routes'
|
||||||
|
export * from './products'
|
||||||
|
export * from './user'
|
||||||
|
7
src/types/products.ts
Normal file
7
src/types/products.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export type WineType = 'white' | 'amber' | 'rose' | 'red'
|
||||||
|
|
||||||
|
export type Viticulture = 'biodynamic' | 'organic' | 'conventional'
|
||||||
|
|
||||||
|
export type BottleClosure = 'cork' | 'crown-seal' | 'screwcap'
|
||||||
|
|
||||||
|
export type Availability = 'in stock' | 'out of stock' | 'discontinued'
|
@ -1,5 +1,6 @@
|
|||||||
export enum Routes {
|
export enum Routes {
|
||||||
Users = '/users',
|
Users = '/users',
|
||||||
NostrEvents = '/nostr',
|
NostrEvents = '/nostr',
|
||||||
Reviews = '/reviews'
|
Reviews = '/reviews',
|
||||||
|
Wines = '/wines'
|
||||||
}
|
}
|
||||||
|
5
src/types/user.ts
Normal file
5
src/types/user.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum UserRole {
|
||||||
|
User = 'user',
|
||||||
|
Reviewer = 'reviewer',
|
||||||
|
Producer = 'producer'
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user