window.pipedriveLeadboosterConfig = { base: leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster on juba olemas') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() GraphQL/MongoDB API kasutuselevõtt Netlify funktsioonide abil - The Codest
The Codest
  • Meie kohta
  • Teenused
    • Tarkvaraarendus
      • Frontend arendus
      • Backend arendus
    • Staff Augmentation
      • Frontend arendajad
      • Backend arendajad
      • Andmeinsenerid
      • Pilveinsenerid
      • QA insenerid
      • Muud
    • See nõuandev
      • Audit ja nõustamine
  • Tööstusharud
    • Fintech & pangandus
    • E-commerce
    • Adtech
    • Healthtech
    • Tootmine
    • Logistika
    • Autotööstus
    • IOT
  • Väärtus
    • CEO
    • CTO
    • Tarnejuht
  • Meie meeskond
  • Case Studies
  • Tea kuidas
    • Blogi
    • Kohtumised
    • Veebiseminarid
    • Ressursid
Karjäärivõimalused Võtke ühendust
  • Meie kohta
  • Teenused
    • Tarkvaraarendus
      • Frontend arendus
      • Backend arendus
    • Staff Augmentation
      • Frontend arendajad
      • Backend arendajad
      • Andmeinsenerid
      • Pilveinsenerid
      • QA insenerid
      • Muud
    • See nõuandev
      • Audit ja nõustamine
  • Väärtus
    • CEO
    • CTO
    • Tarnejuht
  • Meie meeskond
  • Case Studies
  • Tea kuidas
    • Blogi
    • Kohtumised
    • Veebiseminarid
    • Ressursid
Karjäärivõimalused Võtke ühendust
Tagasi nool TAGASI
2021-05-13
Tarkvaraarendus

GraphQL/MongoDB API kasutuselevõtt Netlify funktsioonide abil

The Codest

Pawel Rybczynski

Software Engineer

Eesmärgid Esialgne häälestamine Paigaldame sõltuvused Alustame Kõigepealt lisame tsconfig.json põhikataloogi: Nüüd loome src/server.ts serverite rakenduste jaoks. Seejärel lisame kaks funktsiooni: üks kohaliku serveri jaoks ja teine lambda jaoks. OK, meil ei ole ühtegi resolverit ega tüübimääratlust, seega peame looma mõned. Oletame, et alguses tahame [...]

Eesmärgid

  1. Konfigureerige nii kohalikke kui ka lambda-servereid.
  2. Ühendage mõlemad MongoDB-ga.
  3. Rakendage põhiline autentimine.
  4. Serverless Apollo kasutuselevõtt GraphQL API koos Netlify'ga.
  5. Kasutage Typescript.

Esialgne seadistamine

npm init -y

Paigaldage sõltuvused

npm install --save typescript graphql aws-lambda @types/aws-lambda

Alustame

Kõigepealt lisage tsconfig.json põhikataloogi:

 {
 "compilerOptions": {
 "target": "es5",
 "module": "commonjs",
 "allowJs": true,
 "strict": true,
 "esModuleInterop": true,
 "skipLibCheck": true,
 "forceConsistentCasingInFileNames": true
 },
 "include": [ts", "src/**/*.ts", "src/**/*.js"],
 "exclude": ["node_modules"]
 }

Nüüd loome src/server.ts serverite rakendamiseks. Seejärel lisage kaks funktsiooni: üks kohaliku serveri jaoks ja teine lambda jaoks.

// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda";
import { ApolloServer } from "apollo-server";


const createLambdaServer = () =>
  new ApolloServerLambda({
    typeDefs,
    resolvers,
    introspection: true,
    playground: true,
    },
  });

const createLocalServer = () =>
  new ApolloServer({
    typeDefs,
    resolvers,
    introspection: true,
    playground: true,
    },
  });

eksport { createLambdaServer, createLocalServer };

OK, meil ei ole ühtegi resolverit ega tüübimääratlust, nii et me peame need looma. Oletame, et kõigepealt tahame luua kasutajaid ja saada nende kohta infot.

// src/schemas.ts
const { gql } = require("apollo-server-lambda");

const userSchema = gql`
  type User {
    id: ID!
    email: String!
    name: String!
  }

  type Query {
    user(id: ID!): User!
  }

  type Mutation {
    createUser(name: String!, email: String!, password: String!): User!
  }
`;

Kui te ei ole sellega tuttav, Apollo koostas väga kena õpetuse

Nüüd loome kasutaja resolveri ühe päringu ja ühe mutatsiooniga.

// src/resolvers.ts
const userResolver = {
  Query: {
    user: async (parent, args, context, info) => {
      {...}
    },
  },
  Mutatsioon: {
    createUser: async (parent, args, context, info) => {
      {...}
    },
  },
};

Aga meil ei ole andmeid... Parandame selle 😉.

Ärge unustage importida tüübimääratlust ja resolverit serverisse.

// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda";
import { ApolloServer } from "apollo-server";

import { typeDefs } from "./schemas";
import { resolvers } from "./resolvers";

{...}

MongoDB-ga ühendamine mongoose kaudu

Nüüd on hea aeg luua ühendus meie andmebaasiga. Antud juhul on selleks MongoDB. See on tasuta ja seda on lihtne hooldada. Aga enne seda paigaldame veel kaks sõltuvust:

npm install --save mongoose dotenv

Esimene samm on luua kasutajamudel.

// src/model.ts
import mongoose, { Document, Error, Schema } from "mongoose";

export type User = Document & {
  _id: string,
  email: string,
  name: string,
  password: string,
};

delete mongoose.connection.models["User"];

const UserSchema: Schema = new Schema({
  email: {
    type: String,
    required: true,
    unique: true,
  },
  name: {
    type: String,
    required: true,
    minLength: 3,
    maxLength: 32,
  },
  password: {
    type: String,
    required: true,
  },
});

export const userModel = mongoose.model  ("User", UserSchema);

Muuda paroolid turvalisemaks

Esmalt turvalisus! Turvalistame nüüd oma paroole hashinguga.

npm install --save bcrypt @types/bcrypt

Nüüd rakendage paroolide turvalisus vahenduseelses tagasikutses. Pre-middleware funktsioonid täidetakse üksteise järel, kui iga middleware kutsub järgmist. Selleks, et paroolid oleksid turvalised, kasutame tehnikat, mis genereerib soola ja hashi eraldi funktsioonikõnedel.

// src/model.ts
import bcrypt from "bcrypt";
{...}
const SALT_WORK_FACTOR: number = 10;

UserSchema.pre("save", function (next) {
  const user = this as User;
  if (!this.isModified("password")) return next();

  bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
    if (err) return next(err);

    bcrypt.hash(user.password, salt, function (err, hash) {
      if (err) return next(err);
      user.password = hash;
      next();
    });
  });
});
{...}

Seejärel lisage meetod comparePasswords meetodile UserSchema:

// src/model.ts
{...}
UserSchema.methods.comparePasswords = function (
  candidatePassword: string,
  cb: (err: Error | null, same: boolean | null) => void
) {
  const user = this as User;
  bcrypt.compare(candidatePassword, user.password, (err, isMatch) => {
    if (err) {
      return cb(err, null);
    }
    cb(null, isMatch);
  });
};
{...}

Ja muidugi, modifitseeri kasutaja tüüpi.

type comparePasswordFunction = (
  kandidaatPassword: string,
  cb: (err: Error, isMatch: boolean) => void
) => void;

eksport type User = Document & {
  _id: string,
  email: string,
  name: string,
  password: string,
  comparePasswords: comparePasswordFunction,
};

Nüüd saame korraldada ühenduse serverite ja andmebaaside vahel. MONGODB_URI on keskkonnamuutuja, mis sisaldab ühenduse loomiseks vajalikku ühendusstringi. Saate selle oma klastri paneelilt pärast MongoDB atlas kontole sisselogimist. Pange see sisse .env

// .env
MONGODB_URI = ...;

Ärge unustage alati, et lisada see fail .gitignore. Suurepärane! Nüüd lisame funktsiooni, mis võimaldab ühendada db.

// src/server.ts
import mongoose, { Connection } from "mongoose";
{...}
let cachedDb: Connection;

const connectToDatabase = async () => { {
  if (cachedDb) return;

  await mongoose.connect(process.env.MONGODB_URI || "", {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: false,
    useCreateIndex: true,
  });
  cachedDb = mongoose.connection;
};
{...}

Kontekst on objekt, mida jagavad kõik lahendajad. Selle pakkumiseks tuleb lihtsalt lisada ApolloServeri konstruktorisse konteksti initsialiseerimisfunktsioon. Teeme seda.

// src/server.ts
import { userModel } from "./models/user.model";
{...}
const createLambdaServer = async () =>
  new ApolloServerLambda({
    typeDefs,
    resolvers,
    introspection: true,
    playground: true,
    context: async () => { {
      await connectToDatabase();

      return {
        models: {
          userModel,
        },
      };
    },
  });

const createLocalServer = () =>
  new ApolloServer({
    typeDefs,
    resolvers,
    introspection: true,
    playground: true,
    context: async () => { {
      await connectToDatabase();

      return {
        models: {
          userModel,
        },
      };
    }
  });

Nagu te näete, oleme ka möödasõitnud userModel läbi kontekst. Nüüd saame uuendada resolverit:

// resolvers.ts
const userResolver = {
  Query: {
    user: async (_, { email, name, password }, { models: { userModel } }) => {
      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
  },
  Mutatsioon: {
    createUser: async (_, { id }, { models: { userModel } }) => {
      const user = await userModel.create({ email, name, password });
      return user;
    },
  },
};

Paistab kena! Nüüd loo põhiline serveri instants:

// src/index.ts
import { createLocalServer } from "./server";
require("dotenv").config();

const port = process.env.PORT || 4000;

const server = createLocalServer();

server.listen(port).then(({ url }) => {
  console.log(`Server ir töötab aadressil ${url}`);
});

Kohaliku serveri käivitamine Nodemoniga

Viimane asi enne käivitamist, lisage nodemon.json

{
  "watch": ["src"],
  "ext": ".ts,.js",
  "ignore": [],
  "exec": "ts-node --transpile-only ./src/index.ts"
}

Lisa skript, et package.json

"skriptid": {
    "start": "nodemon"
  },

Ja jookse!

npm start

Sellist infot peaksite saama terminali sees:

Server ir töötab aadressil http://localhost:4000/

Kui jah, siis avage http://localhost:4000/ teie brauseris.

GraphQLi mänguväljak peaks ilmuma. Loome uue kasutaja!

createUser.png

Vaadake, kuidas see andmebaasis välja näeb.

andmebaasJohnDoe.png

Lahe! Kõik toimib hästi!

Proovime saada infot kasutajate kohta.

userWithoutAuth.png

Ja lisage salasõna väli...

userPassword.png

Tore! Me saame vea Välja "parool" ei saa päringusse sisestada väljal "User".". Nagu te võite tagasi vaadata, ei lisanud me seda välja kasutajatüübi määratluse sees. See on seal meelega olemas. Me ei peaks võimaldama mingit parooli või muid tundlikke andmeid küsida.

Teine asi... Me saame kasutaja andmed ilma autentimise... see ei ole hea lahendus. Me peame seda parandama.

Aga enne...

Codegeni konfigureerimine

Kasutame GraphQL kood generaatoriga, et saada ühilduv baastüüp, mis põhineb meie skeemil.

npm install --save @graphql-codegen/cli @graphql-codegen/introspection
@graphql-codegen/typescript @graphql-codegen/typescript-resolvers

Loo codegen.yml

ülekirjutamine: true
skeem: "http://localhost:4000"
genereerib:
  ./src/generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-resolvers"
  ./graphql.schema.json:
    plugins:
      - "introspection"

Lisa codegen skript, et package.json

"skriptid": {
    "start": "nodemon",
    "codegen": "graphql-codegen --config ./codegen.yml": "graphql-codegen --config ./codegen.yml",
  },

Seejärel, kui kohalik server töötab, käivitage skript:

npm run codegen

Edu korral saate teate:

  √ Parseerimise konfiguratsioon
  √ Väljundite genereerimine

Kui saate selle teabe, peaks ilmuma kaks faili:

  • graphql.schema.json põhikataloogis
  • graphql.ts vastloodud teekonnal src/genereeritud

Meid huvitab rohkem teine. Kui avate selle, märkate kena tüübistruktuuri.

Nüüd saame parandada oma lahendajaid:

// src/resolvers.ts
import { Resolvers, Token, User } from "./generated/graphql";

const userResolver: Resolvers = {
  Query: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
  },
  Mutatsioon: {
    createUser: async (
      _,
      { email, name, password },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        name,
        password,
      });
      return user;
    },
  },
};

Autentimine

Järgmisena seadistame lihtsa sümbolipõhise autentimise.

npm install --save jsonwebtoken @types/jsonwebtoken

Loo checkAuth funktsiooniga, et kontrollida, kas märgis on kehtiv. Me lisame tulemuse konteksti, et saaksime sellele ligipääsu resolverite sees.

// src/server.ts
import { IncomingHttpHeaders } from "http";
import {
  APIGatewayProxyEvent,
  APIGatewayProxyEventHeaders,
  Context,
} from "aws-lambda";
import jwt from "jsonwebtoken";
{...}
const checkAuth = async ({ token } : APIGatewayProxyEventHeaders | IncomingHttpHeaders ) => {
  if (typeof token === "string") {
    try {
      return await jwt.verify(token, "riddlemethis");
    } catch (e) {
      throw new AuthenticationError(`Your session expired. Sign in again.`);
    }
  }
};
{...}
const createLambdaServer = async (
  { headers }: APIGatewayProxyEvent,
  context: Context
) => {
  return new ApolloServerLambda({
    typeDefs,
    resolvers,
    context: async () => {
      await connectToDatabase();

      const auth = await checkAuth(headers);

      return {
        auth,
        models: {
          userModel,
        },
      };
    },
  });
};

function createLocalServer() {
  return new ApolloServer({
    typeDefs,
    resolvers,
    introspection: true,
    playground: true,
    context: async ({ req: { headers } = {} }) => {
      const auth = await checkAuth(headers);
      await connectToDatabase();

      return {
         auth,
         models: {
          userModel,
        },
      };
    },
  });
}

Samuti on meil vaja viisi sellise sümboli loomiseks. Parim viis on rakendada sisselogimise päring meie resolveri sees.

// resolvers.ts
import { Resolvers, Token, User } from "./generated/graphql";

const userResolver: Resolvers = {
  Query: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      if (!auth) throw new AuthenticationError("Sa ei ole autentitud");

      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
    login: async (
      _,
      { email, password },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.findOne({ email }).exec();

      if (!user) throw new AuthenticationError("Invalid credentials");

      const matchPasswords = bcrypt.compareSync(password, user.password);

      if (!matchPasswords) throw new AuthenticationError("Invalid credentials");

      const token = jwt.sign({ id: user.id }, "riddlemethis", {
        expiresIn: 60,
      });

      return { token };
    },
  },
  Mutatsioon: {
    createUser: async (
      _,
      { email, name, password },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        name,
        password,
      });
      return user;
    },
  },
};

Samuti peate uuendama kasutajatüübi määratlusi tokeni tüübi ja sisselogimise päringu järgi.

tüüp Token {
    token: String!
  }

type Query {
    user(id: ID!): User!
    login(email: String!, password: String!): Token!
  }

Proovime nüüd saada kasutaja ilma sümboolse sümboolse tähiseta

noAuth.png

OK, töötab hästi! Proovige sisse logida

token.png

Proovime uuesti saada kasutajat, kuid seekord on lisatud märgis päistesse

withToken.png

Lahe! Ja mis siis, kui me määrame valed volitused?

wrongEmail.png

Tore!

Ettevalmistused kasutuselevõtuks

Viimane samm: juuruta serverless api koos Netlify'ga!

Kausta loomine lambda peamise kataloogi ja pane kaks faili sinna sisse:

Esmalt sisaldab AWS käsitseja. See loob ApolloServerLambda serveri instantsi ja
seejärel avalikustada käsitseja, kasutades selle instantsi createHandler.

// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } from "???";

eksport const handler = async (
  event: APIGatewayProxyEvent,
  context: Context
) => {
  const server = await createLambdaServer(event, context);

  return new Promise((res, rej) => { {
    const cb = (err: Error, args: any) => (err ? rej(err) : res(args));
    server.createHandler()(event, context, cb);
  });
};

Selle kohta saate rohkem lugeda.

Teine on tsconfig. Oluline osa on outDir valdkond.

// lambda/tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es6",
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "sõlme",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "outDir": "./dist"
  },
  "include": ["./*.ts", "./**/*.ts", "./**/*.js"]
}

Kuid on üks probleem. Me ei saa kasutada /src dir, sest Netlify ei pääse väljapoole lambda kausta. Nii et me peame selle komplekteerima.

npm install --save ncp

See on pakett, mis võimaldab kopeerida kataloogi.

Lisa kimp skript, et package.json

"skriptid": {
    "bundle": "ncp ./src ./lambda/bundle": "ncp ./src ./lambda/bundle",
    "codegen": "graphql-codegen --config ./codegen.yml",
    "start": "nodemon",
  },

Kui te nüüd käivitate npm run bundle, sa san näha, et vastloodud lambda/bundle dir on meil kõik failid src/.

Uuendage imporditee sees lambda/graphql.ts

// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } from "./bundle/server";

{...}

Nüüd saate lisada lambda/bundle dir'i, et .gitignore

Kasutuselevõtmine koos Netlify'ga

Me peame ütlema Netlify'le, milline on ehituskäsk ja kus meie funktsioonid asuvad. Selleks loome netlify.toml file:

// netlify.toml
[build]
  command = "npm run build:lambda"
  functions = "lambda/dist"

Nagu näete, on see sama kataloog, mis on määratletud kui outDir väli lambda/tsconfig.json

Nii peaks teie rakenduse struktuur välja nägema (noh... osa sellest ;))

app
└─lambda
│ └────bundle
│ │ graphql.ts
│ tsconfig.json.ts
└─src
│ └─generated
│ │ │ │ graphql.ts
│ │ │ index.ts
│ │ │ model.ts
│ │ │ resolvers.ts
│ │ │ schemas.ts
│ server.ts
│
│ codegen.yml
│ graphql.schema.json
│ netlify.toml
│ nodemon.json
│ tsconfig.json

Lisa bundle:lambda skript, et package.json

"skriptid": {
    "build:lambda": "npm run bundle && tsc -p lambda/tsconfig.json": "npm run bundle && tsc -p lambda/tsconfig.json",
    "bundle": "ncp ./src ./lambda/bundle",
    "codegen": "graphql-codegen --config ./codegen.yml",
    "start": "nodemon",
  },

Kasutusele

Proovime oma rakendust juurutada Netlify kaudu.

  1. Logige sisse oma Netlify kontole,
  2. Ühendage oma Githubiga,
  3. Vajutage nupule "Uus sait Gitist",
  4. Valige sobiv repo,
  5. Määra ehituskäsk npm run build:lambda,
  6. Lisa keskkonnamuutuja (MONGODB_URI),
  7. Kasutada...

Ja TAAADAAAAAA...

pageNotFound.png

Seda seetõttu, et lõpp-punkt on vaikimisi mitte http://page/ kuid http://page/.netlify/functions/graphql.

withoutRedirect.png

Kuidas seda parandada? See on väga lihtne. Lihtsalt looge _redirects koos:

/ /.netlify/functions/graphql 200!

Võtke uuesti kasutusele ja kontrollige.

redirect.png

Loodan, et teile meeldis! Võite vabalt parandada ja muuta.

Loe edasi:

Kuidas mitte tappa projekti halbade kodeerimistavadega?

Veebirakenduse turvalisus. Target="_blank" haavatavus

Veebirakenduse turvalisus - XSS haavatavus

Seotud artiklid

Tarkvaraarendus

Tulevikukindlate veebirakenduste loomine: The Codest ekspertide meeskonna ülevaade

Avastage, kuidas The Codest paistab skaleeritavate, interaktiivsete veebirakenduste loomisel silma tipptehnoloogiatega, mis pakuvad sujuvat kasutajakogemust kõigil platvormidel. Saate teada, kuidas meie eksperditeadmised aitavad kaasa digitaalsele ümberkujundamisele ja äritegevusele...

THECODEST
Tarkvaraarendus

Top 10 Lätis asuvat tarkvaraarendusettevõtet

Tutvu Läti parimate tarkvaraarendusettevõtete ja nende innovaatiliste lahendustega meie viimases artiklis. Avastage, kuidas need tehnoloogiajuhid saavad aidata teie äri edendada.

thecodest
Enterprise & Scaleups lahendused

Java tarkvaraarenduse põhitõed: A Guide to Outsourcing Successfully

Tutvuge selle olulise juhendiga, kuidas edukalt outsourcing Java tarkvara arendada, et suurendada tõhusust, pääseda ligi eksperditeadmistele ja edendada projekti edu The Codest abil.

thecodest
Tarkvaraarendus

Ülim juhend Poola allhanke kohta

outsourcing kasv Poolas on tingitud majanduslikust, hariduslikust ja tehnoloogilisest arengust, mis soodustab IT kasvu ja ettevõtlussõbralikku kliimat.

TheCodest
Enterprise & Scaleups lahendused

Täielik juhend IT-auditi vahendite ja tehnikate kohta

IT-auditid tagavad turvalised, tõhusad ja nõuetele vastavad süsteemid. Lisateavet nende tähtsuse kohta leiate kogu artiklist.

The Codest
Jakub Jakubowicz CTO & kaasasutajad

Tellige meie teadmistebaas ja jääge kursis IT-sektori eksperditeadmistega.

    Meie kohta

    The Codest - rahvusvaheline tarkvaraarendusettevõte, mille tehnoloogiakeskused asuvad Poolas.

    Ühendkuningriik - peakorter

    • Büroo 303B, 182-184 High Street North E6 2JA
      London, Inglismaa

    Poola - kohalikud tehnoloogiakeskused

    • Fabryczna büroopark, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Varssavi, Poola

      The Codest

    • Kodu
    • Meie kohta
    • Teenused
    • Case Studies
    • Tea kuidas
    • Karjäärivõimalused
    • Sõnastik

      Teenused

    • See nõuandev
    • Tarkvaraarendus
    • Backend arendus
    • Frontend arendus
    • Staff Augmentation
    • Backend arendajad
    • Pilveinsenerid
    • Andmeinsenerid
    • Muud
    • QA insenerid

      Ressursid

    • Faktid ja müüdid koostööst välise tarkvaraarenduspartneriga
    • USAst Euroopasse: Miks otsustavad Ameerika idufirmad Euroopasse ümber asuda?
    • Tech Offshore arenduskeskuste võrdlus: Euroopa (Poola), ASEAN (Filipiinid), Euraasia (Türgi).
    • Millised on CTO ja CIOde peamised väljakutsed?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Copyright © 2025 by The Codest. Kõik õigused kaitstud.

    etEstonian
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch elGreek etEstonian