Merge pull request 'schemas' (#22) from schemas into staging
Reviewed-on: #22
This commit is contained in:
commit
eb0f389e07
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",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"currency-codes-ts": "^3.0.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"mongodb": "^6.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -2971,6 +2973,18 @@
|
||||
"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": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@ -3071,6 +3085,12 @@
|
||||
"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": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
@ -4553,6 +4573,18 @@
|
||||
"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": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@ -5482,7 +5514,6 @@
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.capitalize": {
|
||||
|
@ -26,8 +26,10 @@
|
||||
"license": "ISC",
|
||||
"description": "Cellar Social API",
|
||||
"dependencies": {
|
||||
"currency-codes-ts": "^3.0.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"mongodb": "^6.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import express, { Express } from 'express'
|
||||
import dotenv from 'dotenv'
|
||||
import { connectToDatabase } from './services/database.service'
|
||||
import { usersRouter, nostrRouter, reviewRouter } from './routes'
|
||||
import { usersRouter, nostrRouter, reviewsRouter, winesRouter } from './routes'
|
||||
import { Routes } from './types'
|
||||
|
||||
dotenv.config()
|
||||
@ -13,7 +13,8 @@ connectToDatabase()
|
||||
.then(() => {
|
||||
app.use(Routes.Users, usersRouter)
|
||||
app.use(Routes.NostrEvents, nostrRouter)
|
||||
app.use(Routes.Reviews, reviewRouter)
|
||||
app.use(Routes.Reviews, reviewsRouter)
|
||||
app.use(Routes.Wines, winesRouter)
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server started at http://localhost:${port}`)
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from './user'
|
||||
export * from './nostrEvent'
|
||||
export * from './review'
|
||||
export * from './wine'
|
||||
|
@ -1,10 +1,10 @@
|
||||
export class NostrEvent {
|
||||
constructor(
|
||||
public id: string,
|
||||
public pubkey: string,
|
||||
public created_at: number,
|
||||
public kind: number,
|
||||
public tags: [string][],
|
||||
public content: string
|
||||
public nostrId: string, // nostr unique identifier
|
||||
public pubkey: string, // public key of the event creator
|
||||
public created_at: number, // timestamp
|
||||
public kind: number, // event type, e.g., review, article, comment
|
||||
public tags: [string][], // array of keywords or hashtags
|
||||
public content: string // text content of the event
|
||||
) {}
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ import { ObjectId } from 'mongodb'
|
||||
|
||||
export class Review {
|
||||
constructor(
|
||||
public eventId: string,
|
||||
public productId: string,
|
||||
public rating: number,
|
||||
public reviewText: string,
|
||||
public testingNotes: string[],
|
||||
public id?: ObjectId
|
||||
public eventId: string, // foreign key referencing the nostrEvents collection
|
||||
public productId: string, // unique identifier for the product
|
||||
public rating: number, // numerical rating, e.g., 1-100
|
||||
public reviewText: string, // text content of the review
|
||||
public tastingNotes: string[], // array of tasting notes, e.g., flavours, aromas
|
||||
public id?: ObjectId // database object id
|
||||
) {}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { ObjectId } from 'mongodb'
|
||||
import { UserRole } from '../types'
|
||||
|
||||
export class User {
|
||||
constructor(
|
||||
public name: string,
|
||||
public npub: string[],
|
||||
public role: UserRole,
|
||||
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 './nostr.router'
|
||||
export * from './reviews.router'
|
||||
export * from './wines.router'
|
||||
|
@ -3,12 +3,12 @@ import { collections } from '../services/database.service'
|
||||
import { Review } from '../models'
|
||||
|
||||
// Global Config
|
||||
export const reviewRouter = express.Router()
|
||||
export const reviewsRouter = express.Router()
|
||||
|
||||
reviewRouter.use(express.json())
|
||||
reviewsRouter.use(express.json())
|
||||
|
||||
// GET
|
||||
reviewRouter.get('/', async (_req: Request, res: Response) => {
|
||||
reviewsRouter.get('/', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const reviews = await collections.reviews?.find({}).toArray()
|
||||
|
||||
@ -23,9 +23,10 @@ reviewRouter.get('/', async (_req: Request, res: Response) => {
|
||||
})
|
||||
|
||||
// POST
|
||||
reviewRouter.post('/', async (req: Request, res: Response) => {
|
||||
reviewsRouter.post('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const review = req.body as Review
|
||||
|
||||
const result = await collections.reviews?.insertOne(review)
|
||||
|
||||
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.NostrEvents]?: mongoDB.Collection
|
||||
[DBcollections.Reviews]?: mongoDB.Collection
|
||||
[DBcollections.Wines]?: mongoDB.Collection
|
||||
} = {}
|
||||
|
||||
// Initialize Connection
|
||||
@ -32,10 +33,12 @@ export async function connectToDatabase() {
|
||||
const reviewsCollection: mongoDB.Collection = db.collection(
|
||||
DBcollections.Reviews
|
||||
)
|
||||
const winesCollection: mongoDB.Collection = db.collection(DBcollections.Wines)
|
||||
|
||||
collections.users = usersCollection
|
||||
collections.nostrEvents = nostrEventsCollection
|
||||
collections.reviews = reviewsCollection
|
||||
collections.wines = winesCollection
|
||||
|
||||
console.log(
|
||||
`Successfully connected to database: ${db.databaseName} and collections:
|
||||
|
@ -1,5 +1,6 @@
|
||||
export enum DBcollections {
|
||||
Users = 'users',
|
||||
NostrEvents = 'nostrEvents',
|
||||
Reviews = 'reviews'
|
||||
Reviews = 'reviews',
|
||||
Wines = 'wines'
|
||||
}
|
||||
|
@ -1,2 +1,4 @@
|
||||
export * from './database'
|
||||
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 {
|
||||
Users = '/users',
|
||||
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